JavaSE基础
基础
输入
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String name = scanner.next(); int age = scanner.nextInt(); double weight = scanner.nextDouble(); boolean isSingle = scanner.nextBoolean(); char gender = scanner.next().charAt(0); scanner.close(); } }
二维数组静态初始化
int[][] arr1 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9, 10}}; int[][] arr2 = new int[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9, 10}}; int[][] arr3 = new int[3][3]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9, 10}}; //错误,静态初始化右边new 数据类型[][]中不能写数字
length
length——数组的属性;
length()——String的方法;
size()——集合的方法;
String[] list={"a","b","c"}; System.out.println(list.length); //数组用length String a="apple"; System.out.println(a.length()); //String用length() List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); System.out.println(list.size());//集合用size()
面向对象
super
class Person { protected String name; // protected之后,可以被子类访问 protected int age; public Person(String name, int age) { this.name = name; this.age = age; } } class Student extends Person { //继承Person类 protected int score; public Student(String name, int age, int score) { super(name, age); // 必须super来调用父类的构造函数 this.score = score; } }
static
修饰成员变量、成员方法
public class Student { static String name; // 类变量,可以通过Student.name来访问,也可以通过对象.name来访问,所有对象都共用同一个name int age; // 实例变量,必须通过对象.name来访问 public static void f() {} // 类方法,与类变量同理 public void f2() {} // 实例方法,与实例变量同理 private Student() {} // 构造器前面加上private就不能在其他地方构造这个对象了 }
注意事项
public class Student { static String schoolName; // 类变量 double score; // 实例变量 // 1、类方法中可以直接访问类成员,不可以直接访问实例成员 public static void printHelloWorld() { // 类方法 // 同一个类中,访问类成员,可以省略类名不写 schoolName = "黑马"; printHelloWorld2(); // System.out.println(score); // 报错 // printPass(); // 报错 // this.printPass2(); // 报错 } public static void printHelloWorld2() {} // 类方法 // 2、实例方法中既可以直接访问类成员,也可以直接访问实例成员 // 3、实例方法中可以出现this关键字,类方法中不可以出现this关键字 public void printPass() { //实例方法 schoolName = "黑马2"; printHelloWorld2(); System.out.println(score); printPass2(); this.printPass2(); } public void printPass2() {} // 实例方法 }
代码块
public class Student { static int number = 80; static String schoolName; /* 静态代码块: 格式:static{} 特点:类加载时自动执行,类只会加载一次,所以静态代码块也执行一次 作用:完成类的初始化,例如:对类变量的初始化赋值 */ static { System.out.println("静态代码块执行了~~"); schoolName = "黑马"; } /* 实例代码块 格式:{} 特点:每次创建对象时,执行实例代码块,并在构造器前执行 作用:和构造器一样,都是用来完成对象的初始化,例如:对实例变量进行初始化赋值(常用),可以用于记录日志,有人创建对象,就用实例代码块来记录,减少构造器重复代码 */ { System.out.println("实例代码块执行了~~"); System.out.println("有人创建了对象:" + this); } public Student() {} }
单例设计模式
饿汉式单例
public class A { // 2、定义一个类变量记住类的一个对象 private static A a = new A(); // 1、把类的构造器私有 private A() {} // 3、定义一个类方法返回类的对象 public static A getObject() { // 在外部无法通过构造器来创建对象,只能通过这个类方法来返回一个对象,而且无论调用多少次,只能创建这么一个对象,因为返回的a是类变量,属于类 return a; } }
懒汉式单例
public class A { // 2、定义一个类变量用于存储这个类的一个对象 private static A a; // 1、把类的构造器私有 private A() {} // 3、定义一个类方法,保证第一次调用时才创建一个对象,后面调用时都会用这同一个对象返回 public static A getObject() { if (a == null) { a = new A(); } return a; } }
extends 继承
public class B extends A{}
类B用extends来继承A
权限修饰符
单继承
Java是单继承的(只能有一个爸爸),不支持多继承(一个孩子不能有多个爸爸),支持多层继承(爷爷、爸爸、孩子)
Object类
class A {} class A extends Object {}
这俩等同,定义一个类A,默认继承自Object类
方法重写
子类重写一个参数列表一样的方法,去覆盖父类的这个方法,就是方法重写。
重写后,方法的访问,Java会遵循就近原则。
- 小技巧:用Override注解,可以让编译器检查重写的格式是否正确,代码可读性更好
- 子类重写父类方法时,访问权限必须大于或等于父类该方法的权限
- 重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小
- 私有方法、静态方法不能被重写,会报错
子类访问其他成员
依照就近原则
public class A { String name = "父类名字"; } public class B extends A{ String name = "子类名字"; public void showName() { String name = "局部名字"; System.out.println(name); // 局部名字 System.out.println(this.name); // 子类成员变量 System.out.println(super.name); // 父类成员变量 } }
多态
什么是多态?
多态是在继承/实现情况下的一种现象,表现为:对象多态、行为多态。
多态的前提:有继承/实现关系;存在父类引用子类对象;存在方法重写。
多态的一个注意事项:多态是对象、行为的多态,Java中的属性(成员变量)不谈多态。
多态的具体代码体现
class People{ void run(){ System.out.println("People run"); } } class Teacher extends People{ @Override void run(){ System.out.println("Teacher run"); } } class Student extends People{ @Override void run(){ System.out.println("Student run"); } } public class Test { public static void main(String[] args) { People p1 = new Student(); // 对象多态 p1.run(); // Student run 行为多态 People p2 = new Teacher(); p2.run(); // Teacher run } }
多态的好处:定义方法时,使用父类类型的形参,可以接受一切子类对象,扩展性更强,更便利
多态的缺点:无法使用子类独有的功能。
多态类型转换:
自动类型转换:父类 变量名 = new 子类();
强制类型转换:子类 变量名 = (子类)父类变量; 强制类型转换后可以解决多态无法使用子类独有功能的缺点。
final 终止
final关键字是终止的意思,可以修饰(类,方法,变量)
修饰类:该类被称为最终类,特点是不能被继承了
修饰方法:该方法成为最终方法,特点是不能被重写
修饰变量:该变量只能被赋值一次
注意:final修饰基本类型的变量,变量存储的数据不能被改变。final修饰引用类型的变量,变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的。
static final String SCHOOL_NAME= "传智教育"
static final修饰的成员变量是常量
abstract 抽象
抽象类,顾名思义,没办法实例化,无法创建对象
abstract,修饰类和方法
public abstract class A { public abstract void run(); // 抽象方法只有方法签名,一定不能有方法体 }
抽象类不一定有抽象方法,有抽象方法的类一定是抽象类。
抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现。
一个类继承抽象类,必须重写抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
模板方法设计模式:解决代码重复的问题
模板方法设计模式应该怎么写?
- 定义一个抽象类。
- 在里面定义2个方法,一个是模板方法:放相同的代码里,一个是抽象方法:具体实现交给子类完成
建议使用final关键字修饰模板方法,防止被子类重写
abstract class People{ // 1、定义一个模板方法 final void write() { // 加个final关键字防止它被子类重写 System.out.println("开头"); // 2、模板方法并不清楚正文部分到底应该怎么写,但是它知道子类肯定要写 System.out.println(writeMain()); System.out.println("结尾"); } // 3、设计一个抽象方法写正文,具体的实现交给子类来完成 abstract String writeMain(); } class Student extends People{ @Override String writeMain(){ return "学生正文"; } } class Teacher extends People{ @Override String writeMain(){ return "老师正文"; } } public class Main { public static void main(String[] args) { People p1 = new Student(); People p2 = new Teacher(); p1.write(); p2.write(); } }
interface 接口
interface关键字可以定义一个特殊的结构:接口
public interface 接口名 { // 成员变量(接口中默认为常量) // 成员方法(接口中默认为抽象方法) }
注意:接口不能创建对象;接口是用来被类实现(implements)的,实现接口的类成为实现类
public class 实现类 implements 接口1, 接口2, 接口3,...{ }
一个类可以实现多个接口(接口可以理解为干爹),实现类实现多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类
interface B { void testb1(); void testb2(); } interface C { void testc1(); void testc2(); } class D implements B, C{ @Override public void testb1(){} @Override public void testb2(){} @Override public void testc1(){} @Override public void testc2(){} }
接口的好处:
弥补了类单继承的不足,一个类可以同时实现多个接口
class A extends Student implements Driver, Singer{}
A类的亲爹是Student,干爹是Driver和Singer
JDK8新增接口形式
public interface A{ default void test1(){} private void test2(){} static void test3(){} }
1、默认方法(实例方法):使用default修饰,默认会被加上public修饰,只能使用接口的实现类对象调用
2、私有方法:必须用private修饰
3、类方法(静态方法):使用static修饰,默认会被加上public修饰,只能用接口名来调用
匿名内部类
一种特殊的内部类,不需要为这个类声明名字
new 类或接口(参数值...) { 类体(一般是方法重写); }; new Animal() { @Override public void cry(){ } };
匿名内部类本质就是一个子类,并会立即创建出一个子类对象。
匿名内部类通常作为一个参数传输给方法。
例如
public static void go(Swimming s){ // go函数传入一个Swimming类 s.swim(); // 调用swim方法 } go(new Swimming(){ // 匿名类Swimming作为参数传输给方法 @Override; public void swim(){ System.out.Println("狗游泳飞快"); } });
枚举类
修饰符 enum 枚举类名{ 名称1, 名称2; 其他成员 } public enum A { X, Y, Z; // 相当于 public void go(){ } } 编译后得到以下 public final class A extends java.lang.Enum<A> { public static final A X = new A(); public static final A Y = new A(); public static final A Z = new A(); public static A[] values(); public static A valueOf(java.lang.String); } 可以得出枚举类的特点 枚举类的第一行只能罗列一些名称,这些名称都是常量,并且每个常量记住的都是枚举类的一个对象 枚举类的构造器都是私有的,因此枚举类对外不能创建对象 枚举类都是最终类,不可以被继承 枚举类中,从第二行开始,可以定义类的其他各种成员
抽象枚举
public enum A { X(){ @Override public void go(){...}//因为go是抽象方法,所以要重写 }, Y("张三"){ @Override public void go(){...} }; // 枚举了两个实例 private String name; A(){} // 构造器默认私有的 A(String name){ // 有参构造器 this.name = name; } public abstract void go(); // 抽象方法,此时A是抽象类,不能直接构建对象,所以第一行不能罗列 }
泛型
泛型类
public class Array<E>{ ... } Array<String> arr = new Array<>(); // 使用方法 public class 类名<E, T, ...>{ ... } Array<String, Animal, ...> arr = new Array<>(); // 使用方法 public class 类名<E extends Animal>{ ... } Array<Cat> arr = new Array<>(); // 使用方法
泛型接口
public interface A<E>{...}
泛型方法
public static <T> void test(T t){...}
举个例子
import java.util.ArrayList; public class Main{ public static void main(String[] args){ ArrayList<Car> cars = new ArrayList<>(); go(cars); } // ? 通配符,在使用泛型的时候可以代表一切类型,E T K V是在定义的时候使用 // ? extends Car 可以表示Car以及Car的子类 // ? super Car 可以表示Car以及Car的父类 public static void go(ArrayList<? extends Car> cars){} // public static <T extends Car> void go(ArrayList<T> cars){} }
泛型擦除
泛型是工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了,这就是泛型擦除。
泛型不支持基本数据类型,只能支持对象类型(引用数据类型)
ArrayList<int> list = new ArrayList<>(); // 报错 ArrayList<Integer> list = new ArrayList<>(); // 正确 ArrayList<double> list = new ArrayList<>(); // 报错 ArrayList<Double> list = new ArrayList<>(); // 正确
常用API
String
定义方法
String a = "123"; char[] chars = {'1', '2', '3'}; String b = new String(chars); byte[] bytes = {97, 98, 99}; String c = new String(bytes); // 输出变量c,是"abc"
常用方法
String s = "abc"; s.length(); // 返回s的长度 s.charAt(i); // 返回下标i的元素 s.toCharArray(); // 返回一个char数组 s.equals("abc"); // 比较两个字符串 s.equalsIgnoreCase("ABC"); // 忽略大小写 s.substring(int startIndex, int endIndex); // 返回子串,左闭右开 s.substring(int startIndex); // 返回子串,从startIndex到末尾 s.replace("bc", "**"); // 将bc字符串替换成** s.contains("123"); // 判断字符串中是否存在某个字符串 s.startsWith("ab"); // 判断字符串是否以某个字符串为开始 s.split() // 将字符串以某个字符串来分割,返回String数组
注意事项
String类型是不可变的,只要是以"..."给出的字符串对象,存储在常量池中,而且内容相同时只会存储一份
String s1 = ""; String s2 = ""; System.out.println(s1 == s2); // true
但是如果是用new方式创建的字符串对象,每new一次都会产生新的对象放在堆内存中
char[] chars = {'a', 'b', 'c'}; String s1 = new String(chars); String s2 = new String(chars); System.out.println(s1 == s2); // false
案例1
String s2 = new String("abc"); // 创建两个对象,双引号abc创建一个对象放在常量池中,new之后在堆内存中也创建一个对象 String s1 = "abc"; // 创建0个对象,因为常量池已经有abc了,所以不用创建了 System.out.println(s1 == s2); // false
案例2
String s1 = "abc"; // 存放在常量池 String s2 = "ab"; String s3 = s2 + "c"; // 运算,存放在堆内存 System.out.println(s1 == s2); // false
案例3
String s1 = "abc"; String s2 = "a" + "b" + "c"; // 在编译时会直接转化成"abc",以提高执行性能 System.out.println(s1 == s2); // true
ArrayList
ArrayList list = new ArrayList(); // 可以存放任意类型的数据 ArrayList<String> list = new ArrayList<String>(); // 指定只能存放String类型数据 list.add("123"); // 在末尾添加 list.add(1, "456"); // 在指定位置添加 list.get(1); // 获取指定位置的值 list.size(); // 返回集合中元素的个数 list.remove(1); // 删除指定位置的元素,返回被删除的元素值 list.remove("123"); // 删除指定的元素,第一次遇到的,删除成功返回true,否则false list.set(1, "123"); // 修改指定位置的元素值,并返回修改前的元素值
Object类
Object类是所有类的父类
public String toString() 返回对象的字符串表示形式
public boolean equals(Object o) 判断两个对象是否相等,主要是为了子类重写
protected object clone()
Objects类
Objects类是一个工具类
String s1 = "123"; String s2 = "123"; s1.equals(s2); // true Objects.equals(s1, s2); // true
String s1 = null; String s2 = "123"; s1.equals(s2); // 报错了 Objects.equals(s1, s2); // false
Objects.equals(a, b) 先做非空判断,再比较两个对象
Objects.isNull(obj) 判断是否为null
Objects.nonNull(obj) 与上一个相反
包装类
包装类就是把基本类型的数据包装成类
基本数据类型 | 对应的包装类(引用数据类型) |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
Integer a = Integer.valueOf(12); Integer b = 12; // 自动装箱,将int类型转成Integer类型 int c = b; // 自动拆箱,将Integer转成int
为什么要用包装类呢?
因为泛型和集合不支持基本数据类型,只能使用引用数据类型
// ArrayList<int> list = new ArrayList<>(); // 不能这么用 ArrayList<Integer> list = new ArrayList<>();
toString()
Integer a = 23; String s = Integer.toString(a); // "23" // String s = a.toString(); // 同上 // String s = a + ""; // 同上 System.out.println(s + 1); // 231
parseInt() parseDouble()
String s = "123"; String t = "99.1"; int a = Integer.parseInt(s); // 不建议用 int a = Integer.valueOf(s); // 建议用 double b = Double.parseDouble(t); // 不建议用 double b = Double.valueOf(s); // 建议用 System.out.println(a); System.out.println(b);
StringBuilder
StringBuilder代表可变字符串对象,相当于容器,它里面装的字符串是可以改变的,
操作字符串效率高,例如拼接修改;如果字符串较少,或者不需要操作,以及定义字符串变量,建议用String
StringBuilder s = new StringBuilder("123"); // 可传可不传参数 s.append(456); // 增 s.append("789"); s.append(true); s.charAt(0); // 查 s.setCharAt(0, 'd'); // 改 // 支持链式编程 // s.append(456).append("789").append(true); System.out.println(s); // 123456789true s.reverse(); // 反转字符串 s.length(); // 字符串长度 s.toString(); // 将StringBuilder转成String类型
StringBuffer
与StringBuilder的用法一模一样,但是StringBuilder是线程不安全的,StringBuffer是线程安全的。
StringJoiner
JDK8才有的,跟StringBuilder一样,是用来操作字符串的,也可以看成一个容器,创建之后里面的内容是可变的
// 构造器 StringJoiner s = new StringJoiner(", "); // 传入一个参数作为间隔符 StringJoiner s = new StringJoiner(", ", "[", "]"); // 传入三个参数,后两个分别是开始符号和结束符号 s.add("java"); // add添加数据,并返回数据本身 s.add("java").add("java"); // 链式操作 System.out.println(s); // "java, java, java" "[java, java, java]"
Math
方法名 | 说明 |
---|---|
public static int abs(int a) | 绝对值 |
public static double ceil(double a) | 向上取整 |
public static double floor(double a) | 向下取整 |
public static int round(float a) | 四舍五入到整数 |
public static int max(int a, int b) | 求最大值 |
public static double pow(double a, double b) | 求a的b次方 |
public static double random() | 返回[0.0, 1.0)内的随机值 |
System
System.exit(0); 人为中断程序
System.currentTimeMillis(); 返回long类型的数,表示从1970-1-1 0:0:0开始走到此刻的毫秒值,应用例子如下
long time1 = System.currentTimeMillis(); // 程序运行过程 long time2 = System.currentTimeMillis(); System.out.println(time2 - time1); // 计算程序运行时间,单位毫秒
Runtime
代表程序所在的运行环境,Runtime是单例类,对外只提供一个对象。
import java.io.IOException; public class Main { public static void main(String[] args) throws IOException, InterruptedException { Runtime r = Runtime.getRuntime(); // 返回与当前Java应用程序关联的运行时对象 // r.exit(0); // 终止当前运行的虚拟机 System.out.println(r.availableProcessors()); // 虚拟机能获取的处理器数 System.out.println(r.totalMemory() / 1024.0 / 1024.0 + "MB"); // Java虚拟机内存总量,单位字节,1024字节=1K System.out.println(r.freeMemory() / 1024.0 / 1024.0 + "MB"); // Java虚拟机中可用内存量 Process p = r.exec("D:\\Microsoft VS Code\\Code.exe");// 启动某个程序,并返回代表该程序的对象 Thread.sleep(5000); // 让程序在这里暂停5秒 p.destroy(); // 关闭程序 } }
BigDecimal
解决小数运算失真的问题
import java.math.BigDecimal; import java.math.RoundingMode; public class Main { public static void main(String[] args) { double a = 0.1; double b = 0.2; System.out.println(a + b); // 1、把小数转换成字符串然后再转成BigDecimal // BigDecimal a1 = new BigDecimal(Double.toString(a)); // BigDecimal b1 = new BigDecimal(Double.toString(b)); // 推荐以下方式,更简洁 BigDecimal a1 = BigDecimal.valueOf(a); BigDecimal b1 = BigDecimal.valueOf(b); // a1.add(b1); // 加法 // a1.subtract(b1); // 减法 // a1.multiply(b1); // 乘法 // a1.divide(b1, 2, RoundingMode.HALF_UP); // 除法,保留两位小数,并四舍五入 double a2 = a1.doubleValue(); // 将BigDecimal类型转成double类型 } }
时间API
JDK8之前的(不建议用)
Date
Date d = new Date(); // 创建一个Date对象,代表系统当前时间 System.out.println(d); // Mon Aug 28 15:23:44 CST 2023 long time = d.getTime(); // 获取时间毫秒值 System.out.println(time); // 1693207424931 // 把时间毫秒值转换成日期对象,2s之后是多少 time += 2000; Date d2 = new Date(time); System.out.println(d2); // Mon Aug 28 15:23:46 CST 2023 // 直接用setTime来修改 Date d3 = new Date(); d3.setTime(time); System.out.println(d3); // Mon Aug 28 15:23:46 CST 2023
SimpleDateFormat
Date d = new Date(); // 创建一个Date对象,代表系统当前时间 System.out.println(d); // Mon Aug 28 15:21:54 CST 2023 long time = d.getTime(); // 获取时间毫秒值 System.out.println(time); // 1693207314195 // 格式化日期对象,和时间毫秒值 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE a"); /* y 年 M 月 d 日 H 小时 m 分 s 秒 EEE 星期几 a 上午/下午 */ String rs = sdf.format(d); String rs2 = sdf.format(time); System.out.println(rs); // 2023-08-28 15:21:54 周一 下午 System.out.println(rs2); // 2023-08-28 15:21:54 周一 下午 String dateStr = "2022-12-12 12:12:12"; // 创建简单日期格式化对象,指定的时间格式必须与被解析的时间格式一模一样,否则会有bug SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(sdf2.parse(dateStr)); // Mon Dec 12 12:12:12 CST 2022
一个应用案例
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class Main { public static void main(String[] args) throws ParseException { // 案例:秒杀活动,给定开始结束时间,判断某个时间是否在开始结束范围内 String start = "2023年11月11日 0:0:0"; // 开始时间 String end = "2023年11月11日 0:10:0"; // 结束时间 String xj = "2023年11月11日 0:01:18"; // 时间1 String xp = "2023年11月11日 0:10:57"; // 时间2 SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); // 把字符串转换成日期对象 Date startDt = sdf.parse(start); Date endDt = sdf.parse(end); Date xjDt = sdf.parse(xj); Date xpDt = sdf.parse(xp); // 把日期对象转换成时间毫秒值 long startTime = startDt.getTime(); long endTime = startDt.getTime(); long xjTime = startDt.getTime(); long xpTime = startDt.getTime(); // 比较大小 if (xjTime >= startTime && xjTime <= endTime) { System.out.println("小贾秒杀成功"); } else { System.out.println("小贾秒杀失败"); } if (xpTime >= startTime && xpTime <= endTime) { System.out.println("小皮秒杀成功"); } else { System.out.println("小皮秒杀失败"); } } }
Calendar
Calendar now = Calendar.getInstance(); // 系统此刻时间对应的日历对象 int year = now.get(Calendar.YEAR); // 年 int days = now.get(Calendar.DAY_OF_YEAR); // 一年中的第几天 Date d = now.getTime(); // 日历中记录的日期对象 long time = now.getTimeInMillis(); // 时间毫秒值 now.set(Calendar.MONTH, 9); // 修改月份为10月 now.add(Calendar.DAY_OF_YEAR, 100); // 将日历对象中的dayofyear值加上100
JDK8之后的(建议用)
代替Date
- Instant:时间戳 / 时间线
代替SimpleDateFormat
- DateTimeFormatter:用于时间的格式化和解析
代替Calendar
- LocalDate:年、月、日
- LocalTime:时、分、秒
- LocalDateTime:年、月、日,时、分、秒
- ZoneId:时区
- ZoneDateTime:带时区的时间
其他补充
- Period:时间间隔(年、月、日)
- Duration:时间间隔(时、分、秒、纳秒)
LocalDate
import java.time.LocalDate; public class Main { public static void main(String[] args) { // 获取本地日期对象 LocalDate ld = LocalDate.now(); System.out.println(ld); // 获取日期对象中的信息 调用get int year = ld.getYear(); int month = ld.getMonthValue(); int day = ld.getDayOfMonth(); int dayOfYear = ld.getDayOfYear(); int dayOfWeek = ld.getDayOfWeek().getValue(); // 修改某个信息 调用with (原来日期对象并没有修改,返回了一个新的对象) LocalDate ld2 = ld.withYear(2099); // 修改年份 LocalDate ld3 = ld.withMonth(12); // 修改月份 System.out.println(ld); // 2023-08-29 System.out.println(ld2); // 2099-08-29 System.out.println(ld3); // 2023-12-29 // 把某个信息加多少 调用plus LocalDate ld4 = ld.plusYears(2); LocalDate ld5 = ld.plusMonths(2); // 把某个信息减多少 调用minus LocalDate ld6 = ld.minusYears(2); LocalDate ld7 = ld.minusMonths(2); // 获取指定日期的LocalDate对象 调用of LocalDate ld8 = LocalDate.of(2019, 2, 2); LocalDate ld9 = LocalDate.of(2019, 2, 2); // 判断两个日期对象是否相等,在前还是在后,equals isBefore isAfter System.out.println(ld8.equals(ld9)); // true } }
LocalTime
与LocalDate的用法几乎完全一致,只不过是获取时、分、秒、纳秒信息
LocalDateTime
包含了LocalDate和LocalTime的用法
不过LocalDateTime可以转换为LocalDate和LocalTime
import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; public class Main { public static void main(String[] args) { LocalDateTime ldt = LocalDateTime.now(); LocalDate ld = ldt.toLocalDate(); LocalTime lt = ldt.toLocalTime(); LocalDateTime ldt1 = LocalDateTime.of(ld, lt); System.out.println(ldt); // 2023-08-29T17:12:19.007914500 System.out.println(ld); // 2023-08-29 System.out.println(lt); // 17:12:19.007914500 System.out.println(ldt1); // 2023-08-29T17:12:19.007914500 } }
总结
方法名 | 示例 |
---|---|
public static Xxxx now(): 获取系统当前时间对应的对象 | LocalDate ld = LocalDate.now(); LocalDate lt = LocalTime.now(); LocalDateTime ldt = LocalDateTime.now(); |
public static Xxxx of(): 获取指定时间的对象 | LocalDate ld = LocalDate.of(2023, 12, 12); LocalDate lt = LocalTime.of(9, 8, 59); LocalDateTime ldt = LocalDateTime.of(2023, 12, 12, 9, 8, 59); |
方法名 | 说明 |
---|---|
public int getYear() | 获取年 |
public Month getMonth() | 获取月份(January February...) |
public int getMonthValue() public int getMonth().getValue() |
获取月份(1-12) |
public int getDayOfMonth() | 获取日 |
public int getDayOfYear() | 获取当前是一年中的第几天 |
public DayOfWeek getDayOfWeek() | 获取星期几(Monday Tuesday...) |
ZoneId
代表时区Id
import java.time.Clock; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Set; public class Main { public static void main(String[] args) { ZoneId zoneId = ZoneId.systemDefault(); // 获取系统的默认时区 System.out.println(zoneId.getId()); // Asia/Shanghai Set<String> zoneIdSet = ZoneId.getAvailableZoneIds(); // 获取Java 支持的全部时区Id System.out.println(zoneIdSet); // [Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, ... ZoneId zoneId1 = ZoneId.of("America/New_York"); // 把某个时区Id封装成ZoneId对象 ZonedDateTime now = ZonedDateTime.now(zoneId1); // 获取某个时区的ZonedDateTime对象 System.out.println(now); // 2023-08-29T06:48:44.296055-04:00[America/New_York] ZonedDateTime now1 = ZonedDateTime.now(Clock.systemUTC()); // 世界标准世界,比中国要慢8小时 System.out.println(now1); // 2023-08-29T10:48:44.298056Z ZonedDateTime now2 = ZonedDateTime.now(); // 无参的时候返回默认时区的时间 System.out.println(now2); // 2023-08-29T18:48:44.298056+08:00[Asia/Shanghai] } }
Instant
通过获取Instant的对象可以拿到此刻的时间,该时间由两部分组成:从1970-01-01 00:00:00开始走到此刻的总秒数+不够1秒的纳秒数(1秒=10^9纳秒)
作用:记录代码运行时间,或者用于记录某个用户操作某个事件的时间点
传统的Date类,只能精确到毫秒,并且是可变对象
新增的Instant类,可以精确到纳秒,是不可变对象,推荐用Instant代替Date
Instant now = Instant.now(); // 获取Instant对象 now.getEpochSecond(); // 获取从1970-01-01 00:00:00到现在的总秒数 now.getNano(); // 获取不够1秒的纳秒数
DateTimeFormmater
用于时间的格式化和解析
import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class Main { public static void main(String[] args) { // 创建一个日期时间格式化器对象 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // 对时间进行格式化 LocalDateTime now = LocalDateTime.now(); String rs = formatter.format(now); System.out.println(rs); // 2023-08-29 21:55:03 // 格式化时间还有一种方案 String rs2 = now.format(formatter); System.out.println(rs2); // 2023-08-29 21:55:03 // 解析时间:一般使用LocalDateTime提供的解析方法来解析 String dataStr = "2029-01-01 00:00:00"; LocalDateTime ldt = LocalDateTime.parse(dataStr, formatter); System.out.println(ldt); // 2029-01-01T00:00 } }
Period
计算两个日期间隔的年数、月数、天数
import java.time.LocalDate; import java.time.Period; public class Main { public static void main(String[] args) { LocalDate start = LocalDate.of(2019, 2, 2); LocalDate end = LocalDate.of(2020, 1, 1); // 创建Period对象,封装两个日期对象 Period period = Period.between(start, end); // 通过Period对象来获取两个日期对象相差的信息 System.out.println(period.getYears()); // 间隔年份 System.out.println(period.getMonths()); // 间隔月份 System.out.println(period.getDays()); // 间隔天数 } }
Duration
计算两个时间对象相差的天数、小时数、分数、秒数、纳秒数
import java.time.Duration; import java.time.LocalDateTime; public class Main { public static void main(String[] args) { LocalDateTime start = LocalDateTime.of(2020, 2, 2, 11, 10, 10); LocalDateTime end = LocalDateTime.of(2020, 2, 2, 11, 11, 10); // 创建Duration对象,封装两个日期对象 Duration duration = Duration.between(start, end); // 获取两个时间对象间隔的信息 System.out.println(duration.toDays()); // 间隔天 System.out.println(duration.toHours()); // 间隔小时 System.out.println(duration.toMinutes()); // 间隔分钟 System.out.println(duration.toSeconds()); // 间隔秒 System.out.println(duration.toMillis()); // 间隔毫秒 System.out.println(duration.toNanos()); // 间隔纳秒 } }
Arrays类
介绍几个Arrays的API,toString、copyOfRange、copyOf、setAll、sort
import java.util.Arrays; import java.util.function.IntToDoubleFunction; public class Main { public static void main(String[] args) { int[] arr = {1, 2, 3, 4, 5}; System.out.println(arr); // 输出地址[I@4eec7777 // toString 打印数组 System.out.println(Arrays.toString(arr)); // 返回数组内容 [1, 2, 3, 4, 5] // copyOfRange 左闭右开 int[] arr2 = Arrays.copyOfRange(arr, 1, 3); System.out.println(Arrays.toString(arr2)); // [2, 3] // copyOf 拷贝到一个新数组,第二个参数是新数组长度 int[] arr3 = Arrays.copyOf(arr, 10); // setAll 把数组中所有值修改 double[] prices = {60.0, 100.0, 70.1}; Arrays.setAll(prices, new IntToDoubleFunction() { @Override public double applyAsDouble(int value) { return prices[value] * 0.8; } }); System.out.println(Arrays.toString(prices)); // [48.0, 80.0, 56.08] // sort 排序 默认升序 Arrays.sort(prices); System.out.println(Arrays.toString(prices)); // [48.0, 56.08, 80.0] } }
如何将对象进行排序呢?
方法1:让对象实现Comparable接口,然后重写compareTo方法,自己制定比较规则。(自定义排序规则Comparable)
方法2:使用下面这个sort方法,创建Comparator比较器接口的匿名内部类对象,自己制定比较规则。(自定义比较器Comparator)
public static
举例
import java.util.Arrays; import java.util.Comparator; public class Main { public static void main(String[] args) { Student[] students = new Student[4]; students[0] = new Student("小明", 2); students[1] = new Student("小亮", 7); students[2] = new Student("小红", 3); students[3] = new Student("小刚", 1); Arrays.sort(students); // 方法1 Arrays.sort(students, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.age - o2.age; } }); // 方法2 System.out.println(Arrays.toString(students)); // [小刚, 小明, 小红, 小亮] } } class Student implements Comparable<Student> { String name; int age; Student(String name, int age) { this.name = name; this.age = age; } @Override public int compareTo(Student o) { // 约定1:左边对象 大于 右边对象, 返回任意正整数 // 约定2:左边对象 小于 右边对象, 返回任意负整数 // 约定3:左边对象 等于 右边对象, 返回0 // if (this.age > o.age) { // return 1; // } else if (this.age < o.age) { // return -1; // } // return 0; return this.age - o.age; // 升序 // return o.age - this.age; // 降序 } @Override public String toString() { return this.name; } }
JDK8新特性
Lambda表达式
Lambda表达式是JDK8新增的一种语法形式,作用:用于简化匿名内部类的代码写法
只能简化函数式接口的匿名内部类。
什么是函数式接口?有且仅有一个抽象方法的接口
public class Main { public static void main(String[] args) { Swimming s1 = new Swimming() { @Override public void swim() { System.out.println("匿名内部类,学生游泳"); } }; Swimming s2 = () -> { System.out.println("Lambda表达式,学生游泳"); }; s1.swim(); s2.swim(); } } interface Swimming { void swim(); }
像前文提到的Arrays.setAll(),也可以用Lambda表达式来改写
import java.util.Arrays; import java.util.function.IntToDoubleFunction; public class Main { public static void main(String[] args) { // setAll 把数组中所有值修改 double[] prices = {60.0, 100.0, 70.1}; // Arrays.setAll(prices, new IntToDoubleFunction() { // @Override // public double applyAsDouble(int value) { // return prices[value] * 0.8; // } // }); // 匿名类 Arrays.setAll(prices, (int value) -> { return prices[value] * 0.8; }); // Lambda表达式来简化匿名内部类 System.out.println(Arrays.toString(prices)); // [48.0, 80.0, 56.08] } }
Arrays.sort()用Lambda改写
Arrays.sort(students, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { if (o1.age > o2.age) { return 1; } else if (o1.age < o2.age) { return -1; } return 0; } }); // 匿名内部类 Arrays.sort(students, (Student o1, Student o2) -> { if (o1.age > o2.age) { return 1; } else if (o1.age < o2.age) { return -1; } return 0; }); // Lambda表达式
Lambda省略规则
- 参数类型可以不写
- 如果只有一个参数,参数类型可以省略,同时()也要省略
- 如果Lambda表达式中的方法体代码只有一行代码,可以省略大括号,同时必须去掉分号,如果这行代码是return的,也必须去掉return
方法引用
静态方法的引用
类名::静态方法
如果某个Lambda表达式里只是调用一个静态方法,并且前后参数的形式一致,就可以使用静态方法引用
实例方法的引用
对象名::实例方法
如果某个Lambda表达式里只是调用一个实例方法,并且前后参数的形式一致,就可以使用实例方法引用
import java.util.Arrays; import java.util.Comparator; public class Main { public static void main(String[] args) { Student[] students = new Student[4]; students[0] = new Student("小明", 2); students[1] = new Student("小亮", 7); students[2] = new Student("小红", 3); students[3] = new Student("小刚", 1); // 原始写法,对学生类别按照年龄排序 Arrays.sort(students, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.age - o2.age; } }); // Lambda简化写法 Arrays.sort(students, (o1, o2) -> o1.age - o2.age); // Lambda表达式调用一个静态方法 Arrays.sort(students, (o1, o2) -> CompareByData.compareByAge(o1, o2)); // 静态方法引用 Arrays.sort(students, CompareByData::compareByAge); // Lambda表达式调用一个实例方法 CompareByData compare = new CompareByData(); Arrays.sort(students, (o1, o2) -> compare.compareByAgeDesc(o1, o2)); // 实例方法引用 Arrays.sort(students, compare::compareByAgeDesc); System.out.println(Arrays.toString(students)); } } class Student { String name; int age; Student(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return this.name; } } class CompareByData { public static int compareByAge(Student o1, Student o2){ // 静态方法,可以通过类直接调用 return o1.age - o2.age; // 升序 } public int compareByAgeDesc(Student o1, Student o2){ // 必须实例化对象后通过对象调用 return o2.age - o2.age; // 降序 } }
特定类型方法的引用
类型::方法
如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的入参的,则此时就可以使用特定类型方法的引用
import java.util.Arrays; import java.util.Comparator; public class Main { public static void main(String[] args) { String[] names = { "boby","angela","Andy", "dlei" ,"caocao","Babo","jack", "Cici"}; // 忽略大小写排序 Arrays.sort(names, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareToIgnoreCase(o2); // 忽略大小写比较 } }); // Lambda改写 Arrays.sort(names, (o1, o2) -> o1.compareToIgnoreCase(o2)); // 特定类型的方法引用 Arrays.sort(names, String::compareToIgnoreCase); System.out.println(Arrays.toString(names)); } }
构造器的引用
类名::new
如果某个Lambda表达式里只是在创建对象,并且前后参数情况一致,就可以使用构造器引用
import java.util.Arrays; import java.util.Comparator; public class Main { public static void main(String[] args) { // 创建匿名内部类对象 // CreateCar cc = new CreateCar() { // @Override // public Car create(String name, double price) { // return new Car(name, price); // } // }; // 用Lambda表达式简化 // CreateCar cc = (name, price) -> new Car(name, price); // 构造器引用 CreateCar cc = Car::new; Car c = cc.create("奔驰", 49.9); System.out.println(c); } } interface CreateCar{ Car create(String name, double price); } class Car { String name; double price; Car(String name, double price){ this.name = name; this.price = price; } }
Stream流
用于操作集合或者数组的数据
获取Stream流
// 1、调用List、Set的Stream方法 List<String> names = new ArrayList<>(); // Set<String> names = new HashSet<>(); Collections.addAll(names, "张三", "李四", "王五"); Stream<String> stream = names.stream(); // 2、调用Map的Stream方法 Map<String, Double> map = new HashMap<>(); map.put("张三", 1.0); map.put("李四", 2.0); Set<String> keys = map.keySet(); Stream<String> ks = keys.stream(); // map的key的stream流 Collection<Double> values = map.values(); Stream<Double> vs = values.stream(); // map的value的stream流 Set<Map.Entry<String, Double>> entries = map.entrySet(); // map的键值对集合 Stream<Map.Entry<String, Double>> kvs = entries.stream(); // 键值对流 kvs.filter(e -> e.getKey().startsWith("张")).forEach(e -> System.out.println(e.getKey() + ", " + e.getValue())); // 3、获取数组的Stream String[] names2 = {"张三", "李四", "王五"}; Stream<String> s1 = Arrays.stream(names2); Stream<String> s2 = Stream.of(names2);
Stream流常见的中间方法
List<Double> scores = new ArrayList<>(); Collections.addAll(scores, 88.5, 100.0, 71.5, 90.5, 40.0, 30.0); // 需求1,找出成绩大于等于60分的数据,升序 scores.stream().filter(s -> s >= 60).sorted().forEach(System.out::println); List<Student> students = new ArrayList<>(); students.add(new Student("张三", 26, 185.0)); students.add(new Student("张三", 50, 190.0)); students.add(new Student("李四", 27, 160.0)); students.add(new Student("王五", 16, 170.0)); // 需求2,找出年龄在[23, 30]的,降序 students.stream().filter(s -> s.age >= 23 && s.age <= 30).sorted((o1, o2) -> o2.age - o1.age).forEach(System.out::println); // 需求3,找出身高前3高的,limit(n)取前n个 students.stream().sorted((o1, o2) -> Double.compare(o2.height, o1.height)).limit(3).forEach(System.out::println); // 需求4,找出身高倒数2名,skip(n)跳过前n个 students.stream().sorted((o1, o2) -> Double.compare(o1.height, o2.height)).limit(2).forEach(System.out::println); students.stream().sorted((o1, o2) -> Double.compare(o2.height, o1.height)).skip(students.size() - 2).forEach(System.out::println); // 需求5,找出身高超过175的学生的名字,要求去除重复名字, map() 将对象用name来代替,然后用distinct() 去重 students.stream().filter(s -> s.height > 175).map(s -> s.name).distinct().forEach(System.out::println); // concat 合并两个Stream Stream<String> s1 = Stream.of("张三", "李四"); Stream<String> s2 = Stream.of("王五", "刘能"); Stream.concat(s1, s2).forEach(System.out::println);
Stream流的终结方法
forEach()
count()
max()
min()
collect(Collectors.toList()) 返回一个List
collect(Collectors.toSet()) 返回一个Set
collect(Collectors.toMap(a -> a.name, a -> height)) 用name作键,height作值,存入Map
toArray() 存到数组中
正则表达式(没学)
用来校验数据格式是否合法
异常
运行时异常:RuntimeException及其子类(如数组越界)
编译时异常:编译阶段就会出现错误提醒
抛出异常(throws)
在方法上使用throws关键字,可以将方法中的异常抛出去给调用者处理
方法 throws 异常1, 异常2, 异常3...{ ... }
捕获异常(try...catch)
直接捕获程序出现的异常
try{ // 监视可能出现异常的代码 }catch(异常类型1 变量){ // 处理异常 }catch(异常类型2 变量){ // 处理异常 }...
自定义运行时异常
1、定义一个异常类继承RuntimeException
2、重写构造器
3、通过throw new异常类(xxx)来创建异常对象并抛出。
public class ExceptionTest { public static void main(String[] args) { try{ f(1); } catch (Exception e) { e.printStackTrace(); System.out.println("底层出现了bug"); } } public static void f(int a) { if (a > 0) { System.out.println("正确"); } else { // 3、通过throws new异常类(xxx)来创建异常对象并抛出。 throw new MyRuntimeException("a非法"); } } } // 1、定义一个异常类继承RuntimeException public class MyRuntimeException extends RuntimeException { // 2、重写构造器 public MyRuntimeException() {} public MyRuntimeException(String message) { super(message); } }
自定义编译时异常
1、定义一个异常类继承Exception
2、重写构造器
3、通过throw new异常类(xxx)来创建异常对象并抛出。
集合框架
Collection
常用方法
方法名 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(Ee) | 把给定的对象在当前集合中删除 |
public boolean contains(object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数 |
public object[ ] toArray() | 把集合中的元素,存储到数组中 |
遍历方式
迭代器
Collection<String> c = new ArrayList<>(); c.add("java1"); c.add("java2"); Iterator it = c.iterator(); while (it.hasNext()) { System.out.println(it.next()); }
增强for
既可以遍历集合,也可以遍历数组
Collection<String> c = new ArrayList<>(); c.add("java1"); c.add("java2"); for (String s: c) { System.out.println(s); }
Lambda表达式
forEach()方法
Collection<String> c = new ArrayList<>(); c.add("java1"); c.add("java2"); c.forEach(new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }); // 匿名内部类 // 改写成Lambda表达式 c.forEach(s -> System.out.println(s)); // 用方法引用 c.forEach(System.out::println);
List集合
List系列集合特点:有序、可重复、有索引
List的两个实现类ArrayList、LinkedList也具有上面三个特点
ArrayList
基于数组实现
LinkedList
基于双链表实现
Set集合
Set系列集合特点:无序、不重复、无索引
HashSet:无序、不重复、无索引
LinkedHashSet:有序、不重复、无索引
TreeSet:排序、不重复、无索引
HashSet
基于哈希表实现,jdk8之后:数组+链表+红黑树
LinkedHashSet
基于哈希表实现,但是每个元素都多了个双链表机制来记录前后元素的顺序
TreeSet
基于红黑树实现排序
利用TreeSet对Student对象排序的方法
Set
可变参数
是一种特殊的形参,定义在方法、构造器的形参列表里,格式是:数据类型...参数名称
特点:可以不传数据给它、可以传一个或者同时传多个,也可以传一个数组。
好处:可以用来灵活的接受数据。
注意:可变参数在方法内部就是一个数组,一个形参列表中可变参数只能有一个,可变参数必须放在形参列表的最后面。
public class Main { public static void main(String[] args) { test(); test(10); test(1,2,3); } public static void test(int...nums) { System.out.println(nums.length); System.out.println(Arrays.toString(nums)); } }
Map
常用方法
Map<String, Integer> map = new HashMap<>(); map.put(key, value) map.size() map.clear() map.isEmpty() map.get(key) map.remove(key) map.containsKey(key) map.containsValue(value) Set<String> key = map.keySet(); // 获取全部键的集合 Collection<Integer> values = map.values(); // 获取全部values值 map1.putAll(map2); // 把map2添加到map1中
HashMap
无序、不重复、无索引
Map<String, Integer> map = new HashMap<>();
map.put("手表", 100);
map.get("手表");
LinkedHashMap
有序、不重复、无索引
TreeMap
自动根据键来排序、不重复、无索引
集合嵌套
Map<String, List<String>> map = new HashMap<>(); List<String> cities1 = new ArrayList<>(); Collections.addAll(cities1, "南京", "苏州"); map.put("江苏省", cities1); List<String> cities2 = new ArrayList<>(); Collections.addAll(cities2, "杭州", "宁波"); map.put("浙江省", cities2); System.out.println(map);
Deque双端队列
import java.util.Deque; import java.util.LinkedList; public class Main { public static void main(String[] args) { /*Deque本质是双端队列*/ /* 头-------------尾*/ Deque<Integer> deque = new LinkedList<>(); deque.offerFirst(3); // 头增 deque.offerLast(2); // 尾增 deque.pollFirst(); // 头删,如果为空,返回null deque.pollLast(); // 尾删,如果为空,返回null deque.peekFirst(); // 头查,如果为空,返回null deque.peekLast(); // 尾查,如果为空,返回null deque.contains(1); // 判断是否存在某个元素 deque.isEmpty(); // 判断是否为空 /*如果只当做普通队列用的话,用下面的方法,就不用指定First还是Last了*/ deque.offer(1); deque.poll(); deque.peek(); /*如果当做栈,用下面的方法*/ deque.push(1); deque.pop(); deque.peek(); } }
IO流
File:操作文件
IO流:读写数据
常用方法
File对象既可以代表文件、也可以代表文件夹。
File封装的对象仅仅是一个路径名,这个路径可以是存在的,也允许是不存在的。
案例1:文件搜索
需求:从D盘中搜索QQ.exe这个文件,找到后输出其位置,并启动QQ
import java.io.File; public class FileTest1 { public static void main(String[] args) throws Exception { searchFile(new File("D:/"), "QQ.exe"); } /** * 去目录下搜索某个文件 * @param dir 目录 * @param fileName 要搜索的目录名称 */ public static void searchFile(File dir, String fileName) throws Exception { if (dir == null || !dir.exists() || dir.isFile()) { return; } File[] files = dir.listFiles(); if (files != null && files.length > 0) { for (File f : files) { if (f.isFile()) { if (f.getName().equals(fileName)) { System.out.println("找到了: " + f.getAbsolutePath()); // 把QQ启动 Runtime runtime = Runtime.getRuntime(); runtime.exec(f.getAbsolutePath()); } } else { searchFile(f, fileName); } } } } }
案例2:删除指定的非空文件夹
分析:先把目标文件夹下的内容都删掉,再删除文件夹
import java.io.File; public class FileTest1 { public static void main(String[] args) throws Exception { File dir = new File("E:\\java\\javase\\data"); deleteDir(dir); } public static void deleteDir(File dir) { if (dir == null || !dir.exists()) { return; } if (dir.isFile()) { // 如果是文件,那就直接删除 dir.delete(); return; } // dir是文件夹,需要获得里面的一级文件对象 File[] files = dir.listFiles(); if (files == null) { // null表示没有访问权限 return; } if (files.length == 0) { // 文件夹为空直接删除再返回 dir.delete(); return; } // 此时dir是个有内容的文件夹, for (File f : files) { if (f.isFile()) { f.delete(); } else { deleteDir(f); } } dir.delete(); } }
字符集、编码、解码
ASCII字符集:只有英文、数字、符号等,占1个字节。
GBK字符集:汉字占2个字节,英文、数字占1个字节。
UTF-8字符集:汉字占3个字节,英文、数字占1个字节。
import java.util.Arrays; public class FileTest1 { public static void main(String[] args) throws Exception { String data = "a我b"; byte[] bytes = data.getBytes(); // 编码,默认用utf-8 System.out.println(Arrays.toString(bytes)); // [97, -26, -120, -111, 98] byte[] bytes1 = data.getBytes("GBK"); // 用GBK编码 System.out.println(Arrays.toString(bytes1)); // [97, -50, -46, 98] String s1 = new String(bytes); // 解码,默认用utf-8 System.out.println(s1); String s2 = new String(bytes1, "GBK"); // 用GBK解码 System.out.println(s2); } }
IO流
字节输入流 InputStream(读字节数据的)
字节输出流 OutoutStream(写字节数据出去的)
字符输入流 Reader(读字符数据的)
字符输出流 Writer(写字符数据出去的)
字节流适合复制文件,不适合读写文本文件
字符流适合读写文本文件内容
IO流-字节流
FileInputStream FileOutputStream
案例:复制文件
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; public class FileTest1 { public static void main(String[] args) throws Exception { InputStream is = new FileInputStream("E:\\java\\javase\\data\\图片1.png"); OutputStream os = new FileOutputStream("E:\\java\\javase\\data\\图片2.png"); byte[] buffer = new byte[1024]; // 1KB int len; // 记录每次读取了多少个字节 while ((len = is.read(buffer)) != -1) { os.write(buffer, 0, len); } os.close(); is.close(); System.out.println("复制完成"); } }
字节流非常适合做一切文件的复制操作,因为任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题
释放资源的方式
try-catch-finally
try { ... } catch (IOException e) { e.printStackTrace(); } finally { ... }
// 复制文件案例的try-catch-finally import java.io.*; public class FileTest1 { public static void main(String[] args) { InputStream is = null; OutputStream os = null; try { is = new FileInputStream("E:\\java\\javase\\data\\图片1.png"); os = new FileOutputStream("E:\\java\\javase\\data\\图片2.png"); byte[] buffer = new byte[1024]; // 1KB int len; // 记录每次读取了多少个字节 while ((len = is.read(buffer)) != -1) { os.write(buffer, 0, len); } System.out.println("复制完成"); } catch (IOException e) { throw new RuntimeException(e); } finally { try { if (os != null) os.close(); } catch (IOException e) { throw new RuntimeException(e); } try { if (is != null) is.close(); } catch (IOException e) { throw new RuntimeException(e); } } } }
finally代码区的特点:无论try中的程序是正常执行了,还是出现了异常,最后都一定会执行finally区,除非JVM终止。
作用:一般用于在程序执行完成后进行资源的释放操作(专业级做法)。
try-with-resource
JDK7提供的更简单的资源释放方案
try(定义资源1;定义资源2;...) { 可能出现的异常; } catch(异常类名 变量名) { 异常处理的代码; }
import java.io.*; public class FileTest1 { public static void main(String[] args) { try ( InputStream is = new FileInputStream("E:\\java\\javase\\data\\图片1.png"); OutputStream os = new FileOutputStream("E:\\java\\javase\\data\\图片2.png"); // 注意这里只能放资源 // 资源一般指的是最终实现了AutoCloseable接口 ) { byte[] buffer = new byte[1024]; // 1KB int len; // 记录每次读取了多少个字节 while ((len = is.read(buffer)) != -1) { os.write(buffer, 0, len); } System.out.println("复制完成"); } catch (IOException e) { throw new RuntimeException(e); } } }
IO流-字符流
FileReader FileWriter
import java.io.*; public class FileTest1 { public static void main(String[] args) { try ( Reader fr = new FileReader("E:\\java\\javase\\data\\a.txt"); ) { char[] buffer = new char[3]; // 每次读3个字符 int len; while ((len = fr.read(buffer)) != -1) { System.out.println(new String(buffer, 0, len)); } } catch (IOException e) { throw new RuntimeException(e); } } }
import java.io.*; public class FileTest1 { public static void main(String[] args) throws Exception { try ( Writer fw = new FileWriter("E:\\java\\javase\\data\\b.txt"); // 第二个参数为true表示在后面追加写入数据 ) { fw.write('a'); fw.write("\n"); fw.write("b"); } catch (IOException e) { throw new RuntimeException(e); } } }
IO流-缓冲流
缓冲流自带了8KB缓冲池,提高读写数据的性能
字节缓冲流
InputStream is = new FileInputStream("file.txt"); InputStream bis = new BufferedInputStream(is); OutputStream os = new FileOutputStream("file.txt"); OutputStream bos = new BufferedOutputStream(os);
字符缓冲流
Reader fr = new FileReader("file.txt"); BufferedReader br = new BufferedReader(fr); br.readLine(); // 字符缓冲流新增的,读取一行数据,没有数据就返回null Writer fw = new FileWriter("file.txt"); BufferedWriter bw = new BufferedWriter(fw); bw.newLine();
案例:拷贝出师表到另一个文件,恢复顺序
分析: 定义一个缓存字符输入流管道与源文件接通。
定义一个List集合存储读取的每行数据。
定义一个循环按照行读取数据,存入到List集合中去。
对List集合中的每行数据按照首字符编号升序排序。
定义一个缓存字符输出管道与目标文件接通。
遍历List集合中的每个元素,用缓冲输出管道写出并换行。
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必能裨补阙漏,有所广益。 8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。 4.将军向宠,性行淑均,晓畅军事,试用于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。 2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。 1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。 9.今当远离,临表涕零,不知所言。 6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。 7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐托付不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。 5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
import java.io.*; import java.util.*; public class FileTest1 { public static void main(String[] args) { try ( BufferedReader br = new BufferedReader(new FileReader("E:\\java\\javase\\data\\出师表.txt")); BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\java\\javase\\data\\出师表2.txt")); ) { List<String> data = new ArrayList<>(); String line; while ((line = br.readLine()) != null) { data.add(line); } // 将每行前面的序号分割出来然后排序 data.sort(Comparator.comparingInt(o -> Integer.parseInt(o.split("\\.")[0]))); System.out.println(data); for (String ln : data) { bw.write(ln); bw.newLine(); } } catch (Exception e) { e.printStackTrace(); } } }
IO流-转换流
字符输入转换流
InputStreamReader
解决不同编码时,字符流读取文本内容乱码的问题
解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了
import java.io.*; public class FileTest1 { public static void main(String[] args) { try ( // 原始方法,由于gbk.txt是GBK编码的,所以会出现乱码 // BufferedReader br = new BufferedReader(new FileReader("src/gbk.txt")); // 用字符输入转换流解决乱码问题 InputStream is = new FileInputStream("src/gbk.txt"); // 先获取原始字节流 Reader isr = new InputStreamReader(is, "GBK"); // 将其按真实的字符集编码转成字符输入转换流 BufferedReader br = new BufferedReader(isr); // 然后将字符输入转换流转成字符缓冲流 ) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } catch (Exception e) { e.printStackTrace(); } } }
字符输出转换流
OutputStreamWriter
import java.io.*; public class FileTest1 { public static void main(String[] args) { try ( OutputStream os = new FileOutputStream("gbk.txt"); Writer osw = new OutputStreamWriter(os, "GBK"); BufferedWriter bw = new BufferedWriter(osw); ) { bw.write("我是中国人"); } catch (Exception e) { e.printStackTrace(); } } }
IO流-打印流
import java.io.*; public class FileTest1 { public static void main(String[] args) { try ( PrintStream ps = new PrintStream("itheima.txt"); ) { ps.println(1); ps.println("我爱你"); ps.println(1.2); } catch (Exception e) { e.printStackTrace(); } } }
PrintStream和PrintWriter差不多
应用:输出重定向,主要用System.setOut(ps)
import java.io.*; public class FileTest1 { public static void main(String[] args) { System.out.println("1"); System.out.println("2"); try ( PrintStream ps = new PrintStream("itheima.txt"); ) { System.setOut(ps); // 后面的输出就输出到文件中了 System.out.println("3"); System.out.println("4"); } catch (Exception e) { e.printStackTrace(); } } }
IO流-数据流
DataInputStream DataOutputStream
DataOutputStream允许把数据和其类型一并写出去。
DataInputStream用于读取数据输出流写出去的数据
import java.io.*; public class FileTest1 { public static void main(String[] args) { try ( DataOutputStream dos = new DataOutputStream(new FileOutputStream("itheima.txt")); DataInputStream dis = new DataInputStream(new FileInputStream("itheima.txt")); ) { dos.writeInt(97); dos.writeDouble(1.0); System.out.println(dis.readInt()); // 先写的int,就得先读int System.out.println(dis.readDouble()); } catch (Exception e) { e.printStackTrace(); } } }
IO流-序列化流
ObjectOutputStream(对象字节输出流),可以把Java对象进行序列化:把Java对象存入到文件中去。
ObjectInputStream(对象字节输入流),可以把Java对象进行反序列化:把存储在文件中的Java对象读入到内存中来。
public class User implements Serializable { // 注意:对象如果要参与序列化,必须实现序列化接口(java.io.Serializable) private String loginName; private String userName; private int age; private transient String passWord; // 加入transient关键字后,这个变量就不参与序列化了 }
import java.io.*; public class Test { public static void main(String[] args) { try ( ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.txt")); // 进行序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.txt")); // 进行反序列化 ) { User u = new User("admin", "张三", 32, "12345678"); // 序列化对象到文件中 oos.writeObject(u); System.out.println("序列化对象成功"); User user = (User) ois.readObject(); // User{loginName='admin', userName='张三', age=32, passWord='12345678'} System.out.println(user); } catch (Exception e) { e.printStackTrace(); } } }
IO流体系图
FileInputStream 字节输入流
FileOutputStream 字节输出流
FileReader 字符输入流
FileWriter 字符输出流
BufferedInputStream 字节缓冲输入流
字符输出转换流
OutputStreamWriter
特殊文件
Properties
是一个Map集合(键值对集合),但是我们一般不会当集合使用。
核心作用:Properties是用来代表属性文件的,通过Properties可以读写属性文件里的内容。
读取properties文件
import java.io.FileReader; import java.util.Properties; import java.util.Set; public class Test { public static void main(String[] args) throws Exception { // 创建一个Properties对象出来 Properties properties = new Properties(); System.out.println(properties); properties.load(new FileReader("E:\\java\\javase\\src\\users.properties")); System.out.println(properties); Set<String> keys = properties.stringPropertyNames(); // 获取全部键的集合 for (String key : keys) { String value = properties.getProperty(key); System.out.println(key + ", " + value); } properties.forEach((key, value) -> { System.out.println(key + ", " + value); }); } }
写入properties文件
import java.io.FileWriter; import java.util.Properties; public class Test { public static void main(String[] args) throws Exception { Properties properties = new Properties(); properties.setProperty("张无忌", "minmin"); properties.setProperty("殷素素", "cuishan"); properties.setProperty("张翠山", "susu"); properties.store(new FileWriter("E:\\java\\javase\\src\\users2.properties"), "I saved many users"); } }
修改properties文件
import java.io.FileReader; import java.io.FileWriter; import java.util.Properties; public class Test { public static void main(String[] args) throws Exception { // 创建一个Properties对象出来 Properties properties = new Properties(); // 读取文件 properties.load(new FileReader("E:\\java\\javase\\src\\users.properties")); if (properties.containsKey("张无忌")) { properties.setProperty("张无忌", "xiaozhao"); } properties.store(new FileWriter("E:\\java\\javase\\src\\users.properties"), "success"); } }
XML(没学)
XML(全称EXtensible Markup Language,可扩展标记语言)
日志(没学)
多线程
多线程创建
方法一:继承Thread类
1、定义子类MyThread,继承java.lang.Thread,重写run方法
2、创建MyThread对象
3、调用线程对象的start方法启动线程(启动后执行run方法)
// 线程的创建方式一: 继承Thread类 public class Main { public static void main(String[] args) { // main方法默认是由一条主线程负责执行 Thread t = new MyThread(); // 3、创建MyThread线程类的对象代表一个线程 t.start(); // 4、启动线程,自动执行run方法 for (int i = 0; i < 5; i++) { System.out.println("主线程main: " + i); } } } class MyThread extends Thread{ // 1、继承Thread类 @Override public void run() { // 2、重写run方法 for (int i = 0; i < 5; i++) { System.out.println("子线程MyThread: " + i); } } }
优缺点:
- 优点:编码简单
- 缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展
注意:
- 启动线程必须用start,不能用run
- 不要把主线程放在启动子线程之前
方法二:实现Runnable接口
1、定义一个线程任务类,实现Runnable接口,重写run方法
2、创建任务对象
3、把任务对象交给Thread处理
4、start启动
public class Main { public static void main(String[] args) { // 3、创建任务对象 Runnable target = new MyRunnable(); // 4、把任务对象交给线程对象 Thread thread = new Thread(target); thread.start(); for (int i = 0; i < 5; i++) { System.out.println("主线程:" + i); } } } class MyRunnable implements Runnable { // 1、定义一个任务类,实现Runnable接口 // 2、重写run方法 @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("子线程:" + i); } } }
优缺点:
- 优点:任务类只是实现接口,还可以继承其他类、实现其他接口,扩展性更强
- 缺点:多创建一个Runnable对象(也谈不上缺点)
方法二的匿名内部类的写法
Runnable target = new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("子线程:" + i); } } };
方法三:实现Callable接口
前两种线程创建方式都存在一个问题,run方法无法返回值,所以Callable解决了这个问题
1、创建任务对象
- 定义类实现Callable接口,重写call方法,封装要做的事,和要返回的数据
- 把Callable类型的对象封装成FutureTask(线程任务对象)
2、把线程任务对象交给Thread对象
3、调用Thread对象的start方法启动线程
4、线程执行完毕后,通过FutureTask对象的get方法获取线程任务执行的结果
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class Main { public static void main(String[] args) throws ExecutionException, InterruptedException { Callable<Integer> call = new MyCallable(5); // 3、创建Callable对象 FutureTask<Integer> f1 = new FutureTask<>(call); // 4、把Callable对象封装成一个FutureTask对象(任务对象) // FutureTask的作用:FuTureTask实现了Runnable对象,可以在线程执行完毕后,用get获取结果 new Thread(f1).start(); // 5、把任务对象交给Thread对象 int sum = f1.get(); System.out.println(sum); } } class MyCallable implements Callable<Integer> { // 1、实现Callable接口 int n; public MyCallable(int n) {this.n = n;} @Override public Integer call() { // 2、重写Call方法 int sum = 0; for (int i = 0; i < n; i++) { sum += i; } return sum; } }
优缺点
- 优点:线程任务类只是实现接口,可以继承类和实现接口,扩展性强;可以在线程执行完毕后获取线程执行结果
- 缺点:编码复杂一点
Thread常用方法
Thread提供的常用方法 | 说明 |
---|---|
public void run() | 线程的任务方法 |
public void start() | 启动线程 |
public String getName() | 获取当前线程名字 |
public void setName(String name) | 为线程设置名字 |
public static Thread currentThread() | 获取当前执行的线程对象 |
public static void sleep(long time) | 让当前线程睡眠多少毫秒后再执行 |
public final void join() | 让调用这个方法的线程执行完 |
Thread提供的常见构造器 | 说明 |
---|---|
public Thread(String name) | 指定名称 |
public Thread(Runnable target) | 封装Runnable对象成为线程对象 |
public Thread(Runnable target, String name) | 封装Runnable对象成为线程对象,并指定线程名称 |
线程安全
多个线程,同时访问同一个共享资源,且存在修改该资源。
线程同步
利用线程同步来解决线程安全问题,思想是让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
同步代码块
作用:把访问共享资源的核心代码块给上锁,以此保证线程安全
synchronized(同步锁) { 访问共享资源的核心代码 }
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
同步锁注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug
同步方法
作用:把访问共享资源的核心方法给上锁,以此保证线程安全
修饰符 synchronized 返回值类型 方法名称(形参列表) { 访问共享资源的核心代码 }
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行
底层原理:同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。如果方法是实例方法,同步方法默认用this作为锁的对象。如果方法是静态方法,同步方法默认用类名.class作为锁的对象。
Lock锁
Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
final Lock lk = new ReentrantLock(); lk.lock(); 访问共享资源的核心代码 lk.unlock();
通常以下操作,即使核心代码有bug,也可以解锁
final Lock lk = new ReentrantLock(); try { 访问共享资源的核心代码 } catch (Exception e) { e.printStackTrace(); } finally { lk.lock(); }
线程通信
生产者消费者案例
import java.util.*; public class Main { public static void main(String[] args){ Desk desk = new Desk(); new Thread(() -> { while (true) desk.put(); }, "厨师1").start(); new Thread(() -> { while (true) desk.put(); }, "厨师2").start(); new Thread(() -> { while (true) desk.put(); }, "厨师3").start(); new Thread(() -> { while (true) { desk.get(); } }, "吃货1").start(); new Thread(() -> { while (true) { desk.get(); } }, "吃货2").start(); } } class Desk { List<String> list = new ArrayList<>(); public synchronized void put() { try { String name = Thread.currentThread().getName(); // 判断是否有包子 if (list.size() == 0) { list.add(name + "做的包子"); System.out.println(name + "做了个包子"); Thread.sleep(2000); } this.notifyAll(); // 唤醒别人 this.wait(); // 等待自己 } catch (Exception e) { e.printStackTrace(); } } public synchronized void get() { try { String name = Thread.currentThread().getName(); if (list.size() == 1) { System.out.println(name + "吃了" + list.get(0)); list.clear(); Thread.sleep(1000); } this.notifyAll(); this.wait(); } catch (Exception e) { e.printStackTrace(); } } }
线程池
创建线程池
方法一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
方法二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize:指定线程池的核心线程的数量
maximumPoolSize:指定线程池的最大线程数量
keepAliveTime:指定临时线程的存活时间
unit:指定临时线程存活的时间单位(秒、分、时、天)
workQueue:指定线程池的任务队列
threadFactory:指定线程池的线程工厂
handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)
临时线程什么时候创建?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
什么时候会开始拒绝新任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
ExecutorService核心方法
方法名称 | 说明 |
---|---|
void execute(Runnable command) | 执行任务/命令,没有返回值,一般用来执行Runnable任务 |
Future<T> submit(Callable<T> task) | 执行任务,返回未来任务对象获取线程结果,一般拿来执行Callable任务 |
void shutdown() | 等任务执行完毕后关闭线程池 |
List<Runnable> shutdownNow() | 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务 |
线程池处理Runnable任务
import java.util.concurrent.*; public class Main { public static void main(String[] args){ // 创建3个核心线程,最大线程数是5,线程池的任务队列可以容纳4个,临时线程5-3=2 ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); Runnable target = new MyRunnable(); // 前3个进入核心线程 pool.execute(target); pool.execute(target); pool.execute(target); // 中间4个进入任务队列 pool.execute(target); pool.execute(target); pool.execute(target); pool.execute(target); // 最后2个进入临时线程 pool.execute(target); pool.execute(target); // 再来新任务就要拒绝了 pool.execute(target); // 抛异常 pool.shutdown(); // 等线程池的任务全部执行完,再关闭线程池 pool.shutdownNow(); // 代码执行到这里时,立即关闭线程池 } } class MyRunnable implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + "输出666"); try { Thread.sleep(Integer.MAX_VALUE); } catch (Exception e) { throw new RuntimeException(e); } } }
线程池处理Callable任务
import java.util.concurrent.*; public class Main { public static void main(String[] args) throws ExecutionException, InterruptedException { // 创建3个核心线程,最大线程数是5,线程池的任务队列可以容纳4个,临时线程5-3=2 ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); Future<String> f1 = pool.submit(new MyCallable(100)); Future<String> f2 = pool.submit(new MyCallable(200)); Future<String> f3 = pool.submit(new MyCallable(300)); Future<String> f4 = pool.submit(new MyCallable(400)); System.out.println(f1.get()); System.out.println(f2.get()); System.out.println(f3.get()); System.out.println(f4.get()); } } class MyCallable implements Callable<String> { private int n; public MyCallable(int n) { this.n = n; } @Override public String call() { int sum = 0; for (int i = 1; i <= n; i++) { sum += i; } return Thread.currentThread().getName() + "求出了1-" + n + "的和是:" + sum; } }
Executors工具类实现线程池
创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它:
public static ExecutorService newFixedThreadPool(int nThreads)
创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程:
public static ExecutorService newSingleThreadExecutor()
线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了60s则会被回收掉:
public static ExecutorService newCachedThreadPool()
创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
这些方法的底层,都是通过线程池的实现类ExecutorService创建的线程池对象
并发、并行
正在运行的程序(软件)就是一个独立的进程。
线程是属于进程的,一个进程中可以同时运行很多个线程。
进程中的多个线程其实是并发和并行执行的。
并发
进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。
并行
在同一时刻,同时有多个线程在被CPU调度执行
线程生命周期
也就是线程从生到死的过程中,经历的各种状态及状态转换。
Java一共定义了6种状态,在Thread类的内部枚举类中
线程状态 | 说明 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动 |
Runnable(可运行) | 线程已经调用了start(),等待CPU调度 |
Blocked(锁阻塞) | 线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态 |
Waiting(无限等待) | 一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能唤醒 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法(sleep,wait)有超时参数,调用他们将进入Timed Waiting状态 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡 |
网络通信
基本通信架构
CS架构(Client客户端/Server服务端)
BS架构(Browser浏览器/Server服务端)
网络通信三要素
IP地址:设备在网络中的地址,是唯一的标识
端口:应用程序在设备中的唯一标识
协议:连接和数据在网络中传输的规则
IP地址
特殊IP地址:
127.0.0.1、localhost:代表本机IP,只会寻找当前所在的主机
InetAddress
代表IP地址,常用方法如下
名称 | 说明 |
---|---|
public static InetAddress getLocalHost() | 获取本机IP |
public static InetAddress getByName(String host) | 根据Ip地址或域名,返回一个InetAddress对象 |
public String getHostName() | 获取该IP地址对象对应的主机名 |
public String getHostAddress() | 获取该IP地址对象中的ip地址信息 |
public boolean isReachable(int timeout) | 在指定毫秒内,判断主机与该ip对应的主机是否能连通 |
import java.net.InetAddress; public class Main { public static void main(String[] args) throws Exception { // 获取本机IP地址对象的主机名和地址信息 InetAddress ip1 = InetAddress.getLocalHost(); System.out.println(ip1.getHostName()); System.out.println(ip1.getHostAddress()); // 获取指定IP或者域名的IP地址对象 InetAddress ip2 = InetAddress.getByName("www.baidu.com"); System.out.println(ip2.getHostName()); System.out.println(ip2.getHostAddress()); System.out.println(ip2.isReachable(6000)); } }
端口号
端口标记正在计算机设备上运行的应用程序,被规定为一个16位的二进制,范围是0~65535
分类
周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占80,FTP占21)
注册端口:1024~49151,分配给用户进程或某些应用程序
动态端口:49152~65535,一般不固定分配某种进程,而是动态分配
注意:我们自己开发的程序一般使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。
协议
网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。
- OSI网络参考模型:全球网络互联标准
- TCP/IP网络模型:事实上的国际标准
UDP(User Datagram Protocol): 用户数据报协议;TCP(Transmission Control Protocol): 传输控制协议
UDP协议
- 特点:无连接、不可靠通信。
- 不事先建立连接,数据按照包发,一包数据包含:自己的IP、程序端口、目的地IP、程序端口和数据(限制在64KB内)等。
- 发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠的。
- 通信效率高,可用于语音通话,视频直播
TCP协议
- 面向连接、可靠通信。
- TCP的最终目的:要保证在不可靠的信道上实现可靠的传输。
- TCP主要有三个步骤实现可靠传输:三次握手建立可靠连接,传输数据进行确认,四次挥手断开连接。
- 通信效率相对不高,用于网页、文件下载、支付
1、三次握手,目的是确定通信双方收发消息都是正常无问题的,可以建立可靠连接
(1)客户端 发送连接请求
(2)服务端 返回一个响应
(3)客户端 再次发出确认信息,连接建立
2、传输数据进行确认,目的是确保数据传输的可靠性。
3、四次挥手断开连接,目的是确保双方数据的收发都已经完成
(1)客户端 发出断开连接请求
(2)服务端 返回一个响应:稍等(因为服务端可能还没接受完数据呢)
(3)服务端 返回一个响应:确认断开(服务端将数据处理完后就说可以断开啦)
(4)客户端 发出正式确认断开连接
UDP通信
DatagramSocket:用于创建客户端、服务端
public DatagramSocket() | 创建客户端的Socket对象,系统会随机分配一个端口号 |
public DatagramSocket(int port) | 创建服务端的Socket对象,并指定端口号 |
public void send(DatagramPacket dp) | 发送数据包 |
public void receive(DatagramPacket p) | 使用数据包接受数据 |
DatagramPacket:创建数据包
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 创建发出去的数据包对象 |
public DatagramPacket(byte[] buf, int length) | 创建用来接受数据的数据包 |
public int getLength() | 获取数据包,实际接收到的字节个数 |
// Server.java import java.net.DatagramPacket; import java.net.DatagramSocket; public class Server { public static void main(String[] args) throws Exception{ System.out.println("服务端启动"); // 创建一个服务端对象,注册端口 DatagramSocket socket = new DatagramSocket(6666); // 创建一个数据包对象,用于接受数据 byte[] buffer = new byte[1024 * 64]; // 64KB DatagramPacket packet = new DatagramPacket(buffer, buffer.length); // 接受客户端发来的数据 socket.receive(packet); System.out.println("服务端接受数据"); int len = packet.getLength(); // 从字节数组中把接收到的数据打印出来,接受多少就倒出多少 String rs = new String(buffer, 0, len); // 转成String System.out.println(rs); System.out.println(packet.getAddress().getHostAddress()); System.out.println(packet.getPort()); } }
// Client.java import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; public class Client { public static void main(String[] args) throws Exception { // 创建客户端对象 DatagramSocket socket = new DatagramSocket(7777); // 创建数据包对象封装要发出去的数据 byte[] bytes = "我是客户端".getBytes(); DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 6666); // 开始正式发送这个数据包的数据 socket.send(packet); System.out.println("客户端数据发送完毕"); // 释放资源 socket.close(); } }
TCP通信
- 客户端程序是通过java.net包下的Socket类来实现的
public Socket(String host, int port) | 根据指定的服务器ip,端口号请求与服务端建立连接,连接通过,就获得了客户端socket |
public OutputStream getOutputStream() | 获得字节输出流对象 |
public InputStream getInputStream() | 获得字节输入流对象 |
- 服务端是通过java.net包下的ServerSocket类来实现的
// Client.java import java.io.DataOutputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; public class Client { public static void main(String[] args) throws Exception { // 创建Socket对象,并同时请求与服务器程序的连接 Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 8888); // 使用socket对象调用getOutputStream()得到一个字节输出流 OutputStream os = socket.getOutputStream(); // 把低级的字节输出流包装成数据输出流 DataOutputStream dos = new DataOutputStream(os); // 写数据出去 dos.writeUTF("客户端发的消息"); // 释放资源 dos.close(); socket.close(); } }
// Server.java import java.io.DataInputStream; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws Exception{ // 创建ServerSocket对象,同时为服务端注册端口 ServerSocket serverSocket = new ServerSocket(8888); // 使用ServerSocket对象,调用accept方法,等待客户端的连接请求 Socket socket = serverSocket.accept(); // 从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 把原始的字节输入流包装成数据输入流 DataInputStream dis = new DataInputStream(is); // 使用数据输入流读取客户端发送的消息 String rs = dis.readUTF(); System.out.println(rs); // 也可以获取客户端的IP地址 System.out.println(socket.getRemoteSocketAddress()); dis.close(); socket.close(); } }
若要实现多个客户端通信,必须要用多线程
// Client.java import java.io.DataOutputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; import java.util.Scanner; public class Client { public static void main(String[] args) throws Exception { // 创建Socket对象,并同时请求与服务器程序的连接 Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 8888); // 使用socket对象调用getOutputStream()得到一个字节输出流 OutputStream os = socket.getOutputStream(); // 把低级的字节输出流包装成数据输出流 DataOutputStream dos = new DataOutputStream(os); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说"); String msg = sc.nextLine(); if ("exit".equals(msg)) { System.out.println("欢迎您使用"); dos.close(); socket.close(); break; } dos.writeUTF(msg); dos.flush(); } } }
// Server.java import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws Exception{ // 创建ServerSocket对象,同时为服务端注册端口 ServerSocket serverSocket = new ServerSocket(8888); while (true) { // 使用ServerSocket对象,调用accept方法,等待客户端的连接请求 Socket socket = serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress() + "上线了"); // 把这个客户端对应的socket通信管道,交给一个独立的 线程处理 new ServerReadThread(socket).start(); } } }
// ServerReadThread.java import java.io.DataInputStream; import java.io.InputStream; import java.net.Socket; public class ServerReadThread extends Thread{ private Socket socket; public ServerReadThread(Socket socket) { this.socket = socket; } @Override public void run() { try { InputStream is = socket.getInputStream(); DataInputStream dis = new DataInputStream(is); while (true) { try { String msg = dis.readUTF(); System.out.println(msg); } catch (Exception e) { System.out.println("离线了"); } } } catch (Exception e) { e.printStackTrace(); } } }
TCP通信-端口转发
Java高级
单元测试
针对最小的功能单元(方法),编写测试代码对其进行正确性测试。
Junit单元测试框架
- 将Junit框架的jar包导入到项目中(IDEA集成了Junit框架,不需要我们自己手工导入)
- 为需要测试的业务类,定义对应的测试类,并为每个业务方法,编写对应的测试方法(必须:公共、无参、无返回值)
- 测试方法上必须声明@Test注解,然后在测试方法中,编写代码调用被测试的业务方法进行调试
- 开始测试:选中测试方法,右键选择“JUnit运行”,如果测试通过则是绿色,失败是红色。
package com.itheima.d1_junit; import org.junit.Assert; import org.junit.Test; public class StringUtilTest { @Test // 测试方法 public void testPrintNumber() { StringUtil.printNumber("admin"); StringUtil.printNumber(null); } @Test // 测试方法 public void testGetMaxIndex() { int index1 = StringUtil.getMaxIndex("admin"); System.out.println(index1); // 断言机制 Assert.assertEquals("方法内部有bug", 4, index1); } }
注解 | 说明 |
---|---|
@Test | 测试类中的方法必须用它修饰才能成为测试方法,才能启动执行 |
@Before | 用来修饰一个实例方法,该方法会在每一个测试方法前执行 |
@After | 用来修饰一个实例方法,该方法会在每一个测试方法后执行 |
@BeforeClass | 用来修饰一个静态方法,该方法会在所有测试方法前只执行一次 |
@AfterClass | 用来修饰一个静态方法,该方法会在所有测试方法后只执行一次 |
开始执行的方法:初始化资源
执行完之后的方法:释放资源
反射
反射就是:加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器)
步骤
1、 加载类,获取类的字节码:Class对象
Class c1 = Student.class; // 获取Class对象 System.out.println(c1.getName()); // 全类名 System.out.println(c1.getSimpleName()); // 简名 Student System.out.println(c1.getMethods());
2、获取类的构造器
Class c1 = Student.class; Constructor[] constructors = c1.getConstructors(); // 获取public修饰的构造器 Constructor[] constructors = c1.getDeclaredConstructors(); // 获取所有声明的构造器 Constructor constructor = c1.getConstructor(); // 获取无参构造器 Constructor constructor = c1.getConstructor(String.class, int.class); // 获取有参构造器 // 打印构造器的名字以及参数数目 System.out.println(constructor.getName() + ", " + constructor.getParameterCount());
获取类构造器的作用:初始化对象返回 newInstance()
Class c1 = Student.class; Constructor constructor = c1.getConstructor(); // 获取无参构造器 constructor.setAccessible(true); // 禁止检查访问权限 Student student = (Student) constructor.newInstance(); // 初始化对象
3、获取类的成员变量
Class c = Student.class; Field[] fields = c.getDeclaredFields(); // 获取声明的(private protected public)成员变量 for (Field field : fields) { System.out.println(field.getName() + ", " + field.getType()); }
获取成员变量的作用:赋值、取值 set() get()
Class c = Student.class; Field fName = c.getDeclaredField("name"); // 获取name成员变量 // 赋值 Student student = new Student(); fName.setAccessible(true); // 禁止检查访问权限 fName.set(student, "小名"); // 为student对象的name成员变量赋值为“小名” System.out.println(student); // 取值 String name = (String) fName.get(student); System.out.println(name);
4、获取类的成员方法
Class c = Student.class; Method[] methods = c.getDeclaredMethods(); Method run = c.getDeclaredMethod("run"); Student student = new Student(); run.invoke(student);
反射的作用
得到一个类的全部成分然后操作
破坏封装性
设计框架
注解
注解就是Java代码里的特殊标记,比如@Override、@Test等,作用是:让其他程序根据注解信息来决定怎么执行程序
自定义注解
public @interface 注解名称 { public 属性类型 属性名() default 默认值; }
注解的原理
- 注解本质是一个接口,Java中所有注解都是继承了Annotation接口
- @注解(...):其实就是一个实现类对象,实现了该注解以及Annotation接口
元注解
元注解指的是:修饰注解的注解
注解的解析
什么是注解的解析?
就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来
如何解析注解?
指导思想:要解析谁上面的注解,就应该先拿到谁
比如要解析类上面的注解,则应该现获取该类的Class对象,再通过Class对象解析其上面的注解
比如要解析成员方法上的注解,则应该获取到该成员方法的Method对象,再通过Method对象解析其上面的注解
Class、Method、Field、Constructor都实现了AnnotatedElement接口,它们都拥有解析注解的能力
// MyTest.java import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) // 元注解,声明注解的保留周期 @Target({ElementType.TYPE, ElementType.METHOD}) // 声明注解在类、方法中使用 public @interface MyTest { String value(); double aaa() default 100; String[] bbb(); }
// Demo.java @MyTest(value = "类", aaa = 99.1, bbb = {"3", "4"}) // 注解修饰类 public class Demo { @MyTest(value = "方法", aaa = 99.4, bbb = {"1", "2"}) // 注解修饰方法 public void test1() { } }
// AnnotationTest.java import org.junit.Test; import java.lang.reflect.Method; import java.util.Arrays; public class AnnotationTest { @Test public void parseClass() { // 1、先得到Class对象 Class c = Demo.class; // 2、解析类上的注解 if (c.isAnnotationPresent(MyTest.class)) { // 判断c这个对象是否存在MyTest这个注解 MyTest myTest = (MyTest) c.getDeclaredAnnotation(MyTest.class); // 解析类上的注解 System.out.println(myTest.value()); System.out.println(myTest.aaa()); System.out.println(Arrays.toString(myTest.bbb())); } } @Test public void parseMethod() throws Exception { // 1、先得到Class对象 Class c = Demo.class; Method m = c.getDeclaredMethod("test1"); // 2、解析类上的注解 if (m.isAnnotationPresent(MyTest.class)) { // 判断c这个对象是否存在MyTest这个注解 MyTest myTest = (MyTest) m.getDeclaredAnnotation(MyTest.class); // 解析方法上的注解 System.out.println(myTest.value()); System.out.println(myTest.aaa()); System.out.println(Arrays.toString(myTest.bbb())); } } }
动态代理(没学)
本文作者:若乔
本文链接:https://www.cnblogs.com/lijinrun/p/17784364.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步