Java

Java

Murphy Lee Lv2

💡 【 Learn Java!】

Java程序基础

基础概念

  • 整数运算

    • 自增/自减(++/- -)
      • n++ 先引用n再加1
      • ++n先加1再引用n
    • 移位运算
      • 左移(n<<m)x2 右移(n>>m)/2
      • 无符号右移>>>m
      • 强制类型转换(type)(精度丢失)
  • 布尔运算

    • 三元运算符:n ? x : y
  • 字符和字符串

    • 字符:char'' 基本数据类型
    • 字符串String"" 引用类型
    • 转义字符
      • \n 换行
      • \r 回车
      • \t Tab
    • 字符串拼接
      • 单行:+
      • 多行:'''…'''

流程控制

  • 输入和输出

    • 格式化输出

      1
      2
      3
      4
      5
      6
      7
      public class Main {
      public static void main(String[] args) {
      double d = 3.1415926;
      System.out.printf("%.2f\n", d); // 显示两位小数3.14
      System.out.printf("%.4f\n", d); // 显示4位小数3.1416
      }
      }
      • 占位符
        • 占位符对应传入参数,参数类型要和占位符一致
        • %d 整数
        • %f 浮点数
        • %s 字符串
        • %e 科学计数法
        • %x 十六进制
    • 输入

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      import java.util.Scanner;

      public class Main {
      public static void main(String[] args) {
      Scanner scanner = new Scanner(System.in); // 创建Scanner对象
      System.out.print("Input your name: ");
      String name = scanner.nextLine(); // 读取一行输入并获取字符串
      System.out.print("Input your age: ");
      int age = scanner.nextInt(); // 读取一行输入并获取整数
      System.out.printf("Hi, %s, you are %d\n", name, age); // 格式化输出
      }
      }
  • **if**

    • 浮点数判断相等:差值小于某个临界值
    • 引用类型判断相等:equals()
      • 如果s1null,s1.equals(s2) 报错:NullPointerException
      • 采用&&判断 或将判断值作为对象调用equals()
  • **switch**

    • lambda的应用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public class Main {
      public static void main(String[] args) {
      String fruit = "apple";
      int opt = switch (fruit) {
      case "apple" -> 1;
      case "pear", "mango" -> 2;
      default -> 0;
      }; // 注意赋值语句要以;结束
      System.out.println("opt = " + opt);
      }
      }
    • yield返回一个值作为switch语句的返回值

数组操作

面向对象

面向对象基础

  • 方法

    • 可变参数

      • 可变参数用Type...定义,相当于数组类型

      • 示例

        1
        2
        3
        4
        5
        6
        7
        8
        class Group {
        private String[] names;

        public void setNames(String... names) { //setNames(String[] names)
        this.names = names;
        }
        }
        //可变参数改写为String[]类型,需要先构造String[]
    • 参数绑定

      • 基本类型参数的传递:是调用方值的复制
      • 引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象
  • 构造方法

    • 构造方法的名称是类名
  • 方法重载

    • 方法名相同,功能类似,参数不同,称为方法重载Overload 返回值类型通常相同
  • 继承

    • **extends**关键字来实现继承
    • protected
      • 子类无法访问父类的private字段或者private方法
      • protected修饰的字段可以被子类访问(以及子类的子类)
    • **super**
      • 子类不会继承任何父类的构造方法
    • 阻止继承
      • sealed修饰class,并通过permits明确写出能够从该class继承的子类名称
    • 向上转型upcasting
      • 子类类型安全地变为父类类型的赋值
    • 向下转型downcasting
      • instanceof判断一个变量所指向的实例是否是指定类型及其子类
    • 组合
      • 继承是is关系,组合是has关系
  • 多态

    • Polymorphic :实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型
      • 如不同incometax方法
    • 继承可以允许子类Override父类的方法
      • final修饰的方法不能被覆写
      • final修饰的类不能被继承
      • final修饰的field在初始化后不能被修改
      • final修饰局部变量不能被重新赋值
  • 抽象类

    • abstract class 只能用于被继承,由子类实现其定义的抽象方法
  • 接口

    • 一个具体的class去实现一个interface时,需要使用implements关键字

    • 一个类不能从多个类继承,只能继承自另一个,但一个类可以实现多个interface

    • 继承关系

      • 接口比抽象类更抽象

      • 实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它

        1
        2
        3
        List list = new ArrayList(); // 用List接口引用具体子类的实例
        Collection coll = list; // 向上转型为Collection接口
        Iterable it = coll; // 向上转型为Iterable接口
    • default方法

      • 抽象类的普通方法可以访问实例字段
      • interface没有字段,default方法无法访问字段
  • 静态字段和静态方法

    • 静态字段
      • 静态字段为描述class本身的字段(非实例字段)
      • 所有实例共享一个静态字段
      • 推荐用类名来访问静态字段
    • 静态方法
      • 静态方法属于class而不属于实例
      • 用实例方法必须通过一个实例变量,而调用静态方法不需要实例变量,通过类名调用
    • 接口的静态字段
      • interface是一个纯抽象类,所以它不能定义实例字段

      • 可以有静态字段的,并且静态字段必须为final类型

      • interface的字段只能是public static final类型

        1
        2
        3
        4
        5
        public interface Person {
        // 编译器会自动加上public statc final:
        int MALE = 1;
        int FEMALE = 2;
        }
    • 如果有两个class名称相同,只能import其中一个,另一个必须写完整类名
    • 包名推荐使用倒置的域名
  • 作用域

    • 如果一个类内部还定义了嵌套类nested class,那么,嵌套类拥有访问private的权限
    • 包作用域:一个类允许访问同一个package
      • 没有publicprivate修饰的类
      • 没有publicprotectedprivate修饰的字段和方法
      • 同一个包,可以访问package权限的classfieldmethod
    • 局部变量
      • 在方法内部定义的变量称为局部变量
      • 局部变量作用域从变量声明处开始到对应的块结束
      • 方法参数也是局部变量
      • 尽可能缩小局部变量的作用域,尽可能延后声明局部变量
    • public
      • 如果有public类,文件名必须和public类的名字相同
      • 一个.java文件只能包含一个public类,但可以包含多个非public
  • 内部类

    • Inner Class的实例不能单独存在,必须依附于一个Outer Class的实例

      Outer.Inner inner = outer.**new** Inner();

    • Inner Class的作用域在Outer Class内部,所以能访问Outer Class的private字段和方法

    • 匿名类Anonymous Class

      • asyncHello()方法,我们在方法内部实例化了一个Runnable

      • Runnable本身是接口,接口是不能实例化的,所以这里实际上是定义了一个实现了Runnable接口的匿名类,并且通过new实例化该匿名类,然后转型为Runnable

      • Main.java

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        public class Main {
        public static void main(String[] args) {
        Outer outer = new Outer("Nested");
        outer.asyncHello();
        }
        }

        class Outer {
        private String name;

        Outer(String name) {
        this.name = name;
        }

        void asyncHello() {
        Runnable r = new Runnable() {
        @Override
        public void run() {
        System.out.println("Hello, " + Outer.this.name);
        }
        };
        new Thread(r).start();
        }
        }
      • 在定义匿名类的时候就必须实例化它,定义匿名类的写法如下

        1
        2
        3
        Runnable r =new Runnable() {
        // 实现必要的抽象方法...
        };
      • 匿名类也完全可以继承自普通类

        • map1是一个普通的HashMap实例
        • map2是一个匿名类实例,只是该匿名类继承自HashMap
        • map3也是一个继承自HashMap的匿名类实例,并且添加了static代码块初始化数据
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        public class Main {
        public static void main(String[] args) {
        HashMap<String, String> map1 = new HashMap<>();
        HashMap<String, String> map2 = new HashMap<>() {}; // 匿名类!
        HashMap<String, String> map3 = new HashMap<>() {
        {
        put("A", "1");
        put("B", "2");
        }
        };
        System.out.println(map3.get("A"));
        }
        }
    • 静态内部类Static Nested Class

      • static修饰的内部类和Inner Class有很大的不同,它不再依附于Outer的实例,而是一个完全独立的类
      • 因此无法引用Outer.this,但它可以访问Outerprivate静态字段和静态方法
      • 如果把StaticNested移到Outer之外,就失去了访问private的权限

Java核心类

  • 字符串和编码

    • String

      • String是一个引用类型,它本身也是一个class
      • 可以直接用"..."这种字符串字面量表示方法
      • 字符串不可变
    • 字符串比较

      • 使用equals()方法而不能用==
      • String类还提供了多种方法来搜索子串、提取子串
    • 去除首尾空白字符

      • 使用trim()方法可以移除字符串首尾空白字符。空白字符包括空格,\t\r\n

        • trim()并没有改变字符串的内容,而是返回了一个新字符串
      • strip()方法也可以移除字符串首尾空白字符

        • trim()不同的是,类似中文的空格字符\u3000也会被移除
      • String还提供了isEmpty()isBlank()来判断字符串是否为空和空白字符串

        • 空和空白字符串

          1
          2
          3
          4
          "".isEmpty(); // true,因为字符串长度为0
          " ".isEmpty(); // false,因为字符串长度不为0
          " \n".isBlank(); // true,因为只包含空白字符
          " Hello ".isBlank(); // false,因为包含非空白字符
    • 替换字串

      • 根据字符或字符串替换
      • 正则表达式
    • 分割字符串

      • 使用split()方法,并且传入的也是正则表达式
    • 拼接字符串

      • 使用静态方法join(),它用指定的字符串连接字符串数组
    • 格式化字符串

      • formatted()方法和format()静态方法,可以传入其他参数,替换占位符,然后生成新的字符串
      • 占位符
    • 类型转换

      • 任意基本类型或引用类型转换为字符串,使用静态方法valueOf() (重载方法)
      • 字符串转换为其他类型
    • 转换为char[]

      • Stringchar[]类型可以互相转换

      • 示例

        1
        2
        char[] cs = "Hello".toCharArray(); // String -> char[]
        String s = new String(cs); // char[] -> String
      • 如果修改了char[]数组,String并不会改变

    • 字符编码

  • **StringBuilder**

    • 链式操作:类定义的append()方法会返回实例本身this
  • StringJoiner

    • 用分隔符拼接数组
    • String还提供了一个静态方法join()
  • 包装类型

    • 基本类型→引用类型 Auto Boxing + Auto unboxing
    • 所有的包装类型都是不变类
    • 实例的比较使用equals
    • 创建新对象时,优先选用静态工厂方法(Integer.valueOf())而不是new操作符
    • 进制转换:静态方法parseInt()可以把字符串解析成一个整数
    • 整数和浮点数的包装类型都继承自Number
  • JavaBean

    • 一种符合命名规范的class,它通过gettersetter来定义属性
    • 使用Introspector.getBeanInfo()可以获取属性列表
  • 枚举类

    • 使用enum定义枚举类型,编译器编译为final class Xxx extends Enum { … }
    • 通过name()获取常量定义的字符串,不使用toString()
    • 通过ordinal()返回常量定义的顺序
    • 可以为enum编写构造方法、字段和方法
      • enum的构造方法要声明为private,字段声明为final
    • enum适合用在switch语句中
  • 记录类

    • enum类似,不能从Record派生,只能通过record关键字由编译器实现继承

    • record Point(**int** x, **int** y) {}

      • 把上述定义改写为class,相当于

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        final class Point extends Record {
        private final int x;
        private final int y;

        public Point(int x, int y) {
        this.x = x;
        this.y = y;
        }

        public int x() {
        return this.x;
        }

        public int y() {
        return this.y;
        }

        public String toString() {
        return String.format("Point[x=%s, y=%s]", x, y);
        }

        public boolean equals(Object o) {
        ...
        }
        public int hashCode() {
        ...
        }
        }
        /* 除了用final修饰class以及每个字段外
        编译器还自动为我们创建了构造方法,和字段名同名的方法
        以及覆写toString()、equals()和hashCode()方法
        */
    • 构造方法

      • Point的构造方法加上检查逻辑

        1
        2
        3
        4
        5
        6
        7
        public record Point(int x, int y) {
        public Point {
        if (x < 0 || y < 0) {
        throw new IllegalArgumentException();
        }
        }
        }
      • 方法public Point {...}被称为

      • 常见的静态方法of(),用来创建Point

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        public record Point(int x, int y) {
        public static Point of() {
        return new Point(0, 0);
        }
        public static Point of(int x, int y) {
        return new Point(x, y);
        }
        }

        var z = Point.of();
        var p = Point.of(123, 456);
  • BigInteger

    • BigInteger用于表示任意大小的整数;
    • BigInteger是不变类,并且继承自Number
    • BigInteger转换成基本类型时可使用longValueExact()等方法保证结果准确
  • BigDecimal

    • BigDecimal用于表示精确的小数,常用于财务计算;
    • 比较BigDecimal的值是否相等,必须使用compareTo()而不能使用equals()
  • 常用工具类

    REF

    • Math:数学计算
    • Random:生成伪随机数
    • SecureRandom:生成安全的随机数

异常处理

  • Java的异常

    • 异常是一种class,本身带有类型信息
    • 异常可以在任何地方抛出,但只需要在上层捕获,这样就和方法调用分离了
    • Throwable是异常体系的根,它继承自Object
    • Throwable有两个体系:ErrorException
      • Error表示严重的错误
      • Exception则是运行时的错误,它可以被捕获并处理
    • 必须捕获的异常,包括Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常称为Checked Exception。
    • 不需要捕获的异常,包括Error及其子类,RuntimeException及其子类
    • 捕获异常
      • 使用try...catch语句,把可能发生异常的代码放到try {...}中,然后使用catch捕获对应的Exception及其子类

      • 在方法定义的时候,使用throws Xxx表示该方法可能抛出的异常类型。调用方在调用的时候,必须强制捕获这些异常,否则编译器会报错

      • 只要是方法声明的Checked Exception,不在调用层捕获,也必须在更高的调用层捕获。所有未捕获的异常,最终也必须在main()方法中捕获

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        public class Main {
        public static void main(String[] args) {
        try {
        byte[] bs = toGBK("中文");
        System.out.println(Arrays.toString(bs));
        } catch (UnsupportedEncodingException e) {
        System.out.println(e);
        }
        }

        static byte[] toGBK(String s) throws UnsupportedEncodingException {
        // 用指定编码转换String为byte[]:
        return s.getBytes("GBK");
        }
        }
  • 捕获异常

    • 多catch语句

    • finally语句

    • 捕获多种异常

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public static void main(String[] args) {
      try {
      process1();
      process2();
      process3();
      } catch (IOException | NumberFormatException e) { // IOException或NumberFormatException
      System.out.println("Bad input");
      } catch (Exception e) {
      System.out.println("Unknown error");
      }
      }
  • 抛出异常

    • 异常的传播

      • 当某个方法抛出了异常时,如果当前方法没有捕获异常,异常就会被抛到上层调用方法,直到遇到某个try ... catch被捕获为止
    • 抛出异常

      • 创建某个Exception的实例

      • throw语句抛出

      • 示例

        1
        2
        3
        4
        5
        void process2(String s) {
        if (s==null) {
        throw new NullPointerException();
        }
        }
    • 异常屏蔽(Suppressed Exception)

      • 先用origin变量保存原始异常,然后调用Throwable.addSuppressed(),把原始异常添加进来,最后在finally抛出
  • 自定义异常

    • 自定义一个BaseException,从RuntimeException派生

    • 自定义的BaseException应该提供多个构造方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class BaseException extends RuntimeException {
      public BaseException() {
      super();
      }

      public BaseException(String message, Throwable cause) {
      super(message, cause);
      }

      public BaseException(String message) {
      super(message);
      }

      public BaseException(Throwable cause) {
      super(cause);
      }
      }
  • **NullPointerException**

    • 处理NullPointerException
      • 成员变量在定义时初始化

        1
        2
        3
        public class Person {
        private String name = "";
        }
      • 返回空字符串""、空数组而不是null

      • 返回Optional<T>

    • 定位NullPointerException
  • **Assertion**

    • 单元测试与JUnit
  • **Log**

    • JDK Logging

      • 日志可以存档,便于追踪问题
      • 按级别分类
      • java.util.logging来实现日志功能
    • Commons Logging

      • 在静态方法中引用Log,定义一个静态类型变量

        1
        2
        3
        4
        5
        6
        7
        public class Main {
        static final Log log = LogFactory.getLog(Main.class);

        static void foo() {
        log.info("foo");
        }
        }
      • 在实例方法中引用Log,定义一个实例变量

        1
        2
        3
        4
        5
        6
        7
        public class Person {
        protected final Log log = LogFactory.getLog(getClass());

        void foo() {
        log.info("foo");
        }
        }
      • 实例变量log的获取方式是LogFactory.getLog(getClass())子类可以直接使用该log实例

        1
        2
        3
        4
        5
        public class Student extends Person {
        void bar() {
        log.info("bar");
        }
        }
    • Log4j

    • SLF4J和Logback

反射

  • 反射

    • 反射Reflection指程序在运行期可以拿到一个对象的所有信息

    • Class类

      • JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息
      • 通过Class实例获取class信息的方法称为反射Reflection
    • 获取一个classClass实例

      • 通过一个class的静态变量class

        1
        Class cls = String.class;
      • 一个实例变量,通过实例变量提供的getClass()方法

        1
        2
        String s = "Hello";
        Class cls = s.getClass();
      • 通过 class的完整类名,静态方法Class.forName()

        1
        Class cls = Class.forName("java.lang.String");
    • Class实例比较和instanceof

      • instanceof不但匹配指定类型,还匹配指定类型的子类

      • ==判断class实例可以精确地判断数据类型,但不能作子类型比较

      • 通过反射获取该Objectclass信息

        1
        2
        3
        void printObjectInfo(Object obj) {
        Class cls = obj.getClass();
        }
    • 动态加载

      • 运行期根据条件来控制加载class
  • 访问字段

    • 通过Class实例获取所有Field对象
      • Field getField(name):根据字段名获取某个publicfield(包括父类)
      • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
      • Field[] getFields():获取所有publicfield(包括父类)
      • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
    • 首先获取StudentClass实例,然后,分别获取public字段、继承的public字段以及private字段
      • getName():返回字段名称,例如,"name"
      • getType():返回字段类型,也是一个Class实例,例如,String.class
      • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义
    • 字段值
      • 获取字段值
        • 先获取Class实例,再获取Field实例,然后用Field.get(Object)获取指定实例的指定字段的值
      • 设置字段值
        • 通过Field.set(Object, Object)实现的,其中第一个Object参数是指定的实例,第二个Object参数是待修改的值
        • 修改非public字段,需要首先调用setAccessible(true)
  • 调用方法

    • 通过Class实例获取所有Method信息
      • Method getMethod(name, Class...):获取某个publicMethod(包括父类)
      • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
      • Method[] getMethods():获取所有publicMethod(包括父类)
      • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
    • 获取StudentClass实例,然后,分别获取public方法、继承的public方法以及private方法
      • getName():返回方法名称,例如:"getScore"
      • getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class
      • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
      • getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义
    • 调用方法
      • Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致
    • 调用静态方法
      • 如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null
    • 调用非public方法
      • 和Field类似,对于非public方法,可以通过Class.getDeclaredMethod()获取该方法实例
      • Method.setAccessible(true)允许其调用
    • 多态
      • 使用反射调用方法时,遵循多态原则:即总是调用实际类型的覆写方法(如果存在)
  • 调用构造方法

    • 通过Class实例获取Constructor的方法
      • getConstructor(Class...):获取某个publicConstructor
      • getDeclaredConstructor(Class...):获取某个Constructor
      • getConstructors():获取所有publicConstructor
      • getDeclaredConstructors():获取所有Constructor
    • Java反射API提供了Constructor对象,包含一个构造方法所有信息,可以创建一个实例
    • Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题
    • 调用非publicConstructor时,必须首先通过setAccessible(true)设置允许访问
  • 获取继承关系

    • 获取父类的Class

      • 有了Class实例,获取父类的Class

        1
        2
        Class i = Integer.class;
        Class n = i.getSuperclass();
    • 获取interface

      • Class[] getInterfaces()只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型
      • 获取接口的父接口要用getInterfaces()
    • 继承关系

      • 判断一个实例是否是某个类型时,使用instanceof操作符
      • 两个Class实例,要判断一个向上转型是否成立,调用isAssignableFrom()
  • 动态代理

    • Dynamic Proxy:在运行期动态创建某个interface的实例
      • 定义一个InvocationHandler实例,它负责实现接口的方法调用
      • 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数
        • 使用的ClassLoader,通常就是接口类的ClassLoader
        • 需要实现的接口数组,至少需要传入一个接口进去
        • 用来处理接口方法调用的InvocationHandler实例
      • 将返回的Object强制转型为接口

注解

  • 使用注解

    • 注解Annotation是放在Java源码的类、方法、字段、参数前的一种特殊“注释”
      • 注释会被编译器忽略,注解被编译器打包进入class文件,是一种用作标注的“元数据”
    • 注解作用
      • 一、编译器使用的注解
        • @Override:让编译器检查该方法是否正确地实现了覆写
        • @SuppressWarnings:让编译器忽略此处代码产生的警告
        • 这类注解不会被编译进入.class文件
      • 二、工具处理.class文件使用的注解
      • 三、在程序运行期能够读取的注解,它们在加载后一直存在于JVM中
      • 定义一个注解时,还可以定义配置参数
  • 定义注解

    • 使用@interface语法来定义注解

      • 注解的参数类似无参数方法,default默认值。最常用的参数应当命名为value

        1
        2
        3
        4
        5
        public @interface Report {
        int type() default 0;
        String level() default "info";
        String value() default "";
        }
    • 元注解

      • 元注解meta annotation可以修饰其他注解

      • 元注解@Target定义Annotation能够被应用于源码的哪些位置

        • 类或接口:ElementType.TYPE
        • 字段:ElementType.FIELD
        • 方法:ElementType.METHOD
        • 构造方法:ElementType.CONSTRUCTOR
        • 方法参数:ElementType.PARAMETER
        • 示例
          • 定义注解@Report用在方法上,必须添加

            • @Target(ElementType.METHOD
          • 定义注解@Report可用在方法或字段上,可以把@Target注解参数变为数组

            1
            2
            3
            4
            5
            6
            7
            @Target({
            ElementType.METHOD,
            ElementType.FIELD
            })
            public @interface Report {
            ...
            }
      • 元注解@Retention定义Annotation的生命周期

        • 仅编译期:RetentionPolicy.SOURCE
        • 仅class文件:RetentionPolicy.CLASS
        • 运行期:RetentionPolicy.RUNTIME
        • default:@Retention(RetentionPolicy.RUNTIME)
      • 元注解@Repeatable定义Annotation是否可重复

      • 元注解@Inherited定义子类是否可继承父类定义的Annotation

        • @Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效
        • 仅针对class的继承,对interface的继承无效
  • 处理注解

    • 如何读取RUNTIME类型的注解
      • 注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation
      • 读取注解,需要使用反射API
    • 判断某个注解是否存在于ClassFieldMethodConstructor
      • Class.isAnnotationPresent(Class)
      • Field.isAnnotationPresent(Class)
      • Method.isAnnotationPresent(Class)
      • Constructor.isAnnotationPresent(Class)
    • 使用反射API读取Annotation
      • Class.getAnnotation(Class)
      • Field.getAnnotation(Class)
      • Method.getAnnotation(Class)
      • Constructor.getAnnotation(Class)
    • 读取方法参数的注解,先用反射获取Method实例,然后读取方法参数的所有注解

泛型

  • 泛型

    • 泛型是编写模板代码来适应任意类型,如ArrayList<T>,然后为用到的类创建对应的ArrayList<type>
    • ArrayList<T>实现了List<T>接口,可以向上转型为List<T>
  • 使用泛型

    • 省略后面的Number,编译器可以自动推断泛型类型

      1
      List<Number> list = new ArrayList<>();
    • 除了ArrayList<T>使用了泛型,还可以在接口中使用泛型

      • Arrays.sort(Object[])可以对任意数组进行排序,但待排序的元素必须实现Comparable<T>这个泛型接口
  • 编写泛型

    • 编写泛型类

      • 按照某种类型,编写类

      • 标记所有特定类型

      • 特定类型String替换为T,并申明<T>

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        public class Pair<T> {
        private T first;
        private T last;
        public Pair(T first, T last) {
        this.first = first;
        this.last = last;
        }
        public T getFirst() {
        return first;
        }
        public T getLast() {
        return last;
        }
        }
    • 静态方法

      • 编写泛型类时,泛型类型<T>不能用于静态方法

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        public class Pair<T> {
        private T first;
        private T last;
        public Pair(T first, T last) {
        this.first = first;
        this.last = last;
        }
        public T getFirst() { ... }
        public T getLast() { ... }

        // 静态泛型方法应该使用其他类型区分:
        public static <K> Pair<K> create(K first, K last) {
        return new Pair<K>(first, last);
        }
        }// 将静态方法的泛型类型和实例类型的泛型类型区分开
      • 多个泛型类型

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        public class Pair<T, K> {
        private T first;
        private K last;
        public Pair(T first, K last) {
        this.first = first;
        this.last = last;
        }
        public T getFirst() { ... }
        public K getLast() { ... }
        }

        // 使用的时候,需要指出两种类型:
        Pair<String, Integer> p = new Pair<>("test", 123);
        //Java标准库的Map<K, V>就是使用两种泛型类型的例子
  • **Type Erasure**

    • 泛型的局限

      • <T>不能是基本类型

        • 实际类型是ObjectObject类型无法持有基本类型
      • 无法取得带泛型的Class

        • Pair<String>Pair<Integer>类型获取Class时,获取到的是同一个Class,也就是Pair类的Class
        • 所有泛型实例,无论T的类型是什么,getClass()返回同一个Class实例,因为编译后它们全部都是Pair<Object>
      • 无法判断带泛型的类型

      • 不能实例化T类型

      • 要实例化T类型,我们必须借助额外的Class<T>参数

        1
        2
        3
        4
        5
        6
        7
        publicclassPair<T> {private T first;
        private T last;
        public Pair(Class<T> clazz) {
        first = clazz.newInstance();
        last = clazz.newInstance();
        }
        }
      • 借助Class<T>参数并通过反射来实例化T类型,使用的时候必须传入Class<T>

        1
        Pair<String> pair = new Pair<>(String.class);
    • 覆写

      • 泛型方法要防止重复定义方法,例如:public boolean equals(T obj)

      • 定义的equals(T t)方法实际上会被擦拭成equals(Object t),而这个方法是继承自Object

        1
        2
        3
        4
        5
        6
        7
        public class Pair<T> {
        public boolean equals(T t) {
        return this == t;
        }
        }
        // 换个方法名,避开与Object.equals(Object)的冲突
        // same(T t)
    • 泛型继承

      • 一个类可以继承自一个泛型类

        1
        2
        public class IntPair extends Pair<Integer> {
        }
      • 在父类是泛型类型的情况下,编译器就必须把类型T保存到子类的class文件中

      • 在继承了泛型类型的情况下,子类可以获取父类的泛型类型

  • **extends**通配符

    • 使用<? extends Number>的泛型定义称之为上界通配符Upper Bounds Wildcards

    • 泛型类型T的上界限定在Number

    • 方法参数签名setFirst(? extends Number)无法传递任何Number的子类型给setFirst(? extends Number)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class Main {    
      public static void main(String[] args) {
      Pair<Integer> p = new Pair<>(123, 456);
      // incompatible types: Pair<Integer> cannot be converted to Pair<Number>
      // Pair<Integer>不是Pair<Number>的子类,add(Pair<Number>)不接受参数类型Pair<Integer>
      int n = add(p);
      System.out.println(n);
      }

      static int add(Pair<? extends Number> p) { // add(Pair<Number> p)报错
      Number first = p.getFirst();
      Number last = p.getLast();
      return first.intValue() + last.intValue();
      }

      class Pair<T> {...}
      }
    • 作用

      • List<? extends Integer>的限制:
      • 允许调用get()方法获取Integer的引用;
      • 不允许调用set(? extends Integer)方法并传入任何Integer的引用(null除外)
      • 对参数List<? extends Integer>进行只读的方法
  • **super**通配符

    • NumberObjectInteger的父类,setFirst(Number)setFirst(Object)实际上允许接受Integer类型

      1
      2
      3
      4
      void set(Pair<? super Integer> p, Integer first, Integer last) {
      p.setFirst(first);
      p.setLast(last);
      }
      • Pair<? super Integer>表示,方法参数接受所有泛型类型为IntegerInteger父类的Pair类型
    • 作用

      • 允许调用set(? super Integer)方法传入Integer的引用
      • 不允许调用get()方法获得Integer的引用
      • 唯一例外是可以获取Object的引用:Object o = p.getFirst()
      • 方法内部代码对于参数只能写,不能读
  • **extends****super**

    • 作为方法参数,<? extends T>类型和<? super T>类型的区别在于:

      • <? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外)
      • <? super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)
    • Collections类定义的copy()方法

      • 第一个参数是List<? super T>,表示目标List,第二个参数List<? extends T>,表示要复制的List

        1
        2
        3
        4
        5
        6
        7
        8
        9
        public class Collections {
        // 把src的每个元素复制到dest中:
        public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i=0; i<src.size(); i++) {
        T t = src.get(i);
        dest.add(t);
        }
        }
        }
    • PECS原则

      • Producer Extends Consumer Super
        • 如果需要返回T,它是生产者(Producer),使用extends通配符
        • 如果需要写入T,它是消费者(Consumer),使用super通配符
    • 无限定通配符

      • Unbounded Wildcard Type

        1
        2
        void sample(Pair<?> p) {
        }
      • 不允许调用set(T)方法并传入引用(null除外)

      • 不允许调用T get()方法并获取T引用(只能获取Object引用)

      • 只能做null判断

      • 可以用<T>替换,同时它是所有<T>类型的超类

  • 泛型和反射

    • 部分反射API是泛型,如:Class<T>Constructor<T>
    • 可以声明带泛型的数组,但不能直接创建带泛型的数组,必须强制转型
    • 可以通过Array.newInstance(Class<T>, int)创建T[]数组,需要强制转型
    • 谨慎使用泛型和可变参数

集合

  • **Collection**

    • java.util包提供了集合类:Collection,它是除Map外所有其他集合类的根接口
      • List:一种有序列表的集合
      • Set:一种保证没有重复元素的集合
      • Map:一种通过键值(key-value)查找的映射表集合
    • 实现了接口和实现类相分离
      • 有序表的接口是List,具体的实现类有ArrayListLinkedList
      • 支持泛型,我们可以限制在一个集合中只能放入同一种数据类型的元素
    • 集合使用统一的Iterator遍历
  • **List**

    • ArrayList在内部使用了数组来存储所有元素

    • List<E>接口

      • 在末尾添加一个元素:boolean add(E e)
      • 在指定索引添加一个元素:boolean add(int index, E e)
      • 删除指定索引的元素:E remove(int index)
      • 删除某个元素:boolean remove(Object e)
      • 获取指定索引的元素:E get(int index)
      • 获取链表大小(包含元素的个数):int size()
    • 实现List接口

      • ArrayList通过数组实现

      • LinkedList通过链表实现

      • ArrayListLinkedList

        ArrayList LinkedList
        获取指定元素 很快 从头查找
        添加元素到末尾 很快 很快
        指定位置添加或删除 需要移动元素 不需要移动元素
        内存占用 较大
    • List的特点

      • List内部的元素可以重复
      • 允许添加null
    • 创建List

      • 除了ArrayListLinkedList

      • of()方法(不接受null

        1
        List<Integer> list = List.of(1, 2, 5);
    • 遍历List

      • for循环根据索引配合get(int)方法遍历
      • Iterator
        • List的实例调用iterator()方法的时候创建

          1
          2
          3
          4
          5
          6
          7
          8
          9
          public class Main {
          public static void main(String[] args) {
          List<String> list = List.of("apple", "pear", "banana");
          for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
          String s = it.next();
          System.out.println(s);
          }
          }
          }
        • for each循环

          1
          2
          3
          4
          5
          6
          7
          8
          public class Main {
          public static void main(String[] args) {
          List<String> list = List.of("apple", "pear", "banana");
          for (String s : list) {
          System.out.println(s);
          }
          }
          }
        • 实现了Iterable接口的集合类都可以直接用for each循环来遍历

    • ListArray转换

      • 调用toArray()方法直接返回一个Object[]数组

      • toArray(T[])传入一个类型相同的ArrayList内部自动把元素复制到传入的Array

        Integer[] **array** = **list**.toArray(**new** Integer[**list**.size()]);

        Integer[] **array** = **list**.toArray(Integer[]::**new**);

      • Array -→ List

        **List**<Integer> **list** = **List**.of(**array**); (只读)

    • 找出List缺失的数字

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      private static int findMissingNumber2(int start, int end, ArrayList<Integer> list) {

      int sum1 = 0;
      int max = start;
      int min = end;

      for (int n : list) {
      sum1 += n;
      if (n > max) max = n;
      if (n < min) min = n;
      }

      int sum2 = (min + max) * (list.size() + 1) / 2;

      if (start != min) return start;
      if (end != max) return end;

      return sum2 - sum1;
      }
  • **equals**

    • List提供
      • boolean contains(Object o)方法来判断List是否包含某个指定元素
      • int indexOf(Object o)方法返回元素的索引,如果元素不存在,返回-1
      • 放入的实例必须正确覆写equals()方法
    • equals()方法要求
      • 自反性(Reflexive)

      • 对称性(Symmetric)

      • 传递性(Transitive)

      • 一致性(Consistent)

      • null的比较:即x.equals(null)永远返回false

      • 对于Person

        1
        2
        3
        4
        5
        6
        7
        public boolean equals(Object o) {
        if (o instanceof Person p) {
        return this.name.equals(p.name) && this.age == p.age;
        }
        return false;
        }
        // 引用字段比较使用equals(),基本类型字段的比较使用==
      • 简化引用类型的比较,使用Objects.equals()静态方法

        1
        2
        3
        4
        5
        6
        public boolean equals(Object o) {
        if (o instanceof Person p) {
        return Objects.equals(this.name, p.name) && this.age == p.age;
        }
        return false;
        }
  • **Map**

    • Map<K, V>是一种键-值映射表
      • 当调用put(K key, V value)方法时,就把keyvalue做了映射并放入Map
      • 当我们调用V get(K key)时,就可以通过key获取到对应的value
      • 如果key不存在,则返回null
      • List类似,Map也是一个接口,最常用的实现类是HashMap
      • 查询某个key是否存在,调用boolean containsKey(K key)方法
      • Map中不存在重复的key,放入相同的key,只会把原有的value给替换掉
    • 遍历Map
      • for each循环遍历Map实例keySet()方法返回的Set集合,包含不重复的key集合

        1
        for (String key : map.keySet()) {...}
      • for each循环遍历Map对象的entrySet()集合,包含每一个key-value映射

        1
        for (Map.Entry<String, Integer> entry : map.entrySet()) {};
  • 编写**equals****hashCode**

    • Map
      • 作为key的对象必须正确覆写equals()方法,相等的两个key实例调用equals()必须返回true
      • 作为key的对象还必须正确覆写hashCode()方法,且hashCode()方法要严格遵循以下规范
        • 如果两个对象相等,则两个对象的hashCode()必须相等
        • 如果两个对象不相等,则两个对象的hashCode()尽量不要相等
      • 编写equals()hashCode()遵循的原则是
        • equals()用到的用于比较的每一个字段,都必须在hashCode()中用于计算
        • equals()中没有使用到的字段,绝不可放在hashCode()中计算
        • 实现hashCode()方法可以通过Objects.hashCode()辅助方法实现
      • 创建HashMap时指定容量
  • EnumMap

    • 如果Map的key是enum类型,使用EnumMap
  • TreeMap

    • SortedMap 在内部会对Key进行排序,实现类是TreeMap
    • 使用TreeMap时,放入的Key必须实现Comparable接口
      • 如果作为Key的class没有实现Comparable接口,在创建TreeMap时同时必须指定一个自定义排序算法

      • 严格按照compare()规范实现比较逻辑

      • 定义Student类,并用分数score进行排序,高分在前

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        import java.util.*;

        public class Main {
        public static void main(String[] args) {
        Map<Student, Integer> map = new TreeMap<>(new Comparator<Student>() {
        public int compare(Student p1, Student p2) {
        if (p1.score == p2.score) {
        return 0;
        }
        return p1.score > p2.score ? -1 : 1;
        }// or: Integer.compare(int, int)
        });
        map.put(new Student("Tom", 77), 1);
        map.put(new Student("Bob", 66), 2);
        map.put(new Student("Lily", 99), 3);
        for (Student key : map.keySet()) {
        System.out.println(key);
        }
        System.out.println(map.get(new Student("Bob", 66))); // null?
        }
        }

        class Student {
        public String name;
        public int score;
        Student(String name, int score) {
        this.name = name;
        this.score = score;
        }
        public String toString() {
        return String.format("{%s: score=%d}", name, score);
        }
        }
  • Properties

    • 读取配置文件

      • 创建Properties实例

      • 调用load()读取文件

      • 调用getProperty()获取配置(覆盖配置)

        1
        2
        3
        4
        5
        6
        7
        String f = "setting.properties";
        Properties props = new Properties();
        props.load(new java.io.FileInputStream(f));
        // props.load(getClass().getResourceAsStream("/common/setting.properties"));

        String filepath = props.getProperty("last_open_file");
        String interval = props.getProperty("auto_save_interval", "120");
    • 写入配置文件

      • setProperty()修改了Properties实例
      • 写入配置文件使用store()
    • 编码

      • 使用重载方法load(Reader)读取

        1
        2
        3
        Properties props = new Properties();
        props.load(new FileReader("settings.properties", StandardCharsets.UTF_8));
        // InputStream和Reader的区别是一个是字节流,一个是字符流
  • Set

    • Set用于存储不重复的元素集合
      • 将元素添加进Set<E>boolean add(E e)
      • 将元素从Set<E>删除:boolean remove(Object e)
      • 判断是否包含元素:boolean contains(Object e)
    • 放入Set的元素和Map的key类似,都要正确实现equals()hashCode()方法
    • 最常用的Set实现类是HashSet
    • Set接口并不保证有序
      • HashSet是无序的,实现了Set接口,并没有实现SortedSet接口
      • TreeSet是有序的,实现了SortedSet接口
      • 使用TreeSet和使用TreeMap的要求一样
        • 添加的元素必须正确实现Comparable接口
        • 如果没有实现Comparable接口,创建TreeSet时必须传入一个Comparator对象
  • Queue

    • Queue是实现了一个先进先出(FIFO)的有序表

      • int size():获取队列长度
      • boolean add(E)/boolean offer(E):添加元素到队尾
      • E remove()/E poll():获取队首元素并从队列中删除
      • E element()/E peek():获取队首元素但并不从队列中删除
    • 两个方法(要避免把null添加到队列)

      throw Exception 返回false或null
      添加元素到队尾 add(E e) boolean offer(E e)
      取队首元素并删除 E remove() E poll()
      取队首元素但不删除 E element() E peek()
    • LinkedList即实现了List接口,又实现了Queue接口

      1
      2
      3
      4
      // 这是一个List:
      List<String> list = new LinkedList<>();
      // 这是一个Queue:
      Queue<String> queue = new LinkedList<>();
  • PriorityQueue

    • 放入PriorityQueue的元素必须实现Comparable接口,or通过Comparator自定义排序算法,根据元素排序顺序决定出队优先级
  • Deque

    • Double Ended Queue

      Queue Deque
      添加元素到队尾 add(E e) / offer(E e) addLast(E e) / offerLast(E e)
      取队首元素并删除 E remove() / E poll() E removeFirst() / E pollFirst()
      取队首元素但不删除 E element() / E peek() E getFirst() / E peekFirst()
      添加元素到队首 addFirst(E e) / offerFirst(E e)
      取队尾元素并删除 E removeLast() / E pollLast()
      取队尾元素但不删除 E getLast() / E peekLast()
    • Deque接口实际上扩展自Queue

      • 调用xxxFirst()/xxxLast()以便与Queue的方法区分开;
      • 避免把null添加到队列
    • Deque是一个接口,它的实现类有ArrayDequeLinkedList

  • Stack

    • 栈(Stack)是一种后进先出(LIFO)的数据结构
      • 把元素压栈:push(E)
      • 把栈顶的元素“弹出”:pop()
      • 取栈顶元素但不弹出:peek()
    • Deque可以实现Stack的功能
      • push(E)/addFirst(E)
      • pop()/removeFirst()
      • peek()/peekFirst()
    • 作用
      • JVM在处理Java方法调用的时候就会通过栈这种数据结构维护方法调用的层次
        • JVM会创建方法调用栈,每调用一个方法时
          • 先将参数压栈,然后执行对应的方法
          • 当方法返回时,返回值压栈,调用方法通过出栈操作获得方法返回值
          • 嵌套调用过多会造成栈溢出,引发StackOverflowError
      • 整数的进制转换
      • 计算中缀表达式
  • Iterator

    • 通过Iterator对象遍历集合的模式称为迭代器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      import java.util.*;

      public class Main {
      public static void main(String[] args) {
      ReverseList<String> rlist = new ReverseList<>();
      rlist.add("Apple");
      rlist.add("Orange");
      rlist.add("Pear");
      for (String s : rlist) {
      System.out.println(s);
      }
      }
      }

      class ReverseList<T> implements Iterable<T> {

      private List<T> list = new ArrayList<>();

      public void add(T t) {
      list.add(t);
      }

      @Override
      public Iterator<T> iterator() {
      return new ReverseIterator(list.size());
      }

      class ReverseIterator implements Iterator<T> {
      int index;

      ReverseIterator(int index) {
      this.index = index;
      }

      @Override
      public boolean hasNext() {
      return index > 0;
      }

      @Override
      public T next() {
      index--;
      return ReverseList.this.list.get(index);
      }
      }
      }
  • Collections

    • addAll()方法可以给一个Collection类型的集合添加若干元素

      1
      public static boolean addAll(Collection<? super T> c, T... elements) { ... }
    • 创建空集合

      • 创建空List:List<T> emptyList()

      • 创建空Map:Map<K, V> emptyMap()

      • 创建空Set:Set<T> emptySet()

      • 返回的空集合是不可变集合,无法向其中添加或删除元素

      • 集合接口提供的of(T...)方法创建空集合

        1
        2
        List<String> list1 = List.of();
        List<String> list2 = Collections.emptyList();
    • 创建单元素集合

      • 单元素集合也是不可变集合
      • 创建一个元素的List:List<T> singletonList(T o)
      • 创建一个元素的Map:Map<K, V> singletonMap(K key, V value)
      • 创建一个元素的Set:Set<T> singleton(T o)
    • 使用List.of(T...),可以创建空集合,单元素集合,任意个元素的集合

    • 排序:Collections.sort(list);

    • 洗牌:Collections.shuffle(list);

    • 不可变集合

      • 封装成不可变List:List<T> unmodifiableList(List<? extends T> list)
      • 封装成不可变Set:Set<T> unmodifiableSet(Set<? extends T> set)
      • 封装成不可变Map:Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m)
      • 对原始的可变List进行增删是可以的,并且会直接影响到封装后的“不可变”List

IO

  • IO

    • IO是指Input/Output
    • InputStream / OutputStream
      • IO流以byte(字节)为最小单位,因此也称为字节流
    • Reader / Writer
      • 字符流
      • Java提供了ReaderWriter表示字符流,传输的最小数据单位是char
    • 同步和异步
      • Java标准库的java.io包提供了同步IO功能
      • 同步IO
        • 读写IO时代码必须等待数据返回后才继续执行后续代码
        • 优点是代码编写简单,缺点是CPU执行效率低
      • 异步IO
        • 读写IO时仅发出请求,然后立刻执行后续代码
        • 优点是CPU执行效率高,缺点是代码编写复杂
  • File

    • File对象
      • Java的标准库java.io提供了File对象来操作文件和目录
      • 构造File对象时,既可以传入绝对路径,也可以传入相对路径
      • File对象有3种形式表示的路径
        • getPath()返回构造方法传入的路径
        • getAbsolutePath()返回绝对路径
        • getCanonicalPath返回的是规范路径,和绝对路径类似
    • 文件和目录
      • File对象既可以表示文件,也可以表示目录
        • 构造一个File对象不会导致任何磁盘操作
        • 调用File对象的某些方法时才真正进行磁盘操作
      • File对象获取到一个文件时,判断文件的权限和大小
        • boolean canRead()
        • boolean canWrite()
        • boolean canExecute()
        • long length()
    • 创建和删除文件
      • createNewFile()创建一个新文件
      • delete()删除该文件
      • createTempFile()来创建一个临时文件
      • deleteOnExit()在JVM退出时自动删除该文件
    • 遍历文件和目录
      • list()listFiles()列出目录下的文件和子目录名
      • File对象如果表示一个目录
        • boolean mkdir():创建当前File对象表示的目录
        • boolean mkdirs():创建当前File对象表示的目录,并在必要时将不存在的父目录也创建出来
        • boolean delete():删除当前File对象表示的目录,当前目录必须为空才能删除成功
    • Path
      • Path对象位于java.nio.file

      • 如果需要对目录进行复杂的拼接、遍历等操作,使用Path对象更方便

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        import java.io.*;
        import java.nio.file.*;

        public class Main {
        public static void main(String[] args) throws IOException {
        Path p1 = Paths.get(".", "project", "study"); // 构造一个Path对象
        System.out.println(p1);
        Path p2 = p1.toAbsolutePath(); // 转换为绝对路径
        System.out.println(p2);
        Path p3 = p2.normalize(); // 转换为规范路径
        System.out.println(p3);
        File f = p3.toFile(); // 转换为File对象
        System.out.println(f);
        for (Path p : Paths.get("..").toAbsolutePath()) { // 可以直接遍历Path
        System.out.println(" " + p);
        }
        }
        }
  • InputStream

    • InputStream

      • Java标准库提供的最基本的输入流

      • 位于packagejava.io,提供了所有同步IO的功能

      • 不是一个接口,而是一个抽象类,它是所有输入流的超类

      • 抽象类定义的一个最重要的方法就是int read()

      • 方法签名

        1
        public abstract int read() throws IOException;
      • FileInputStreamInputStream的一个子类,从文件流中读取数据

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        public void readFile() throws IOException {
        // 创建一个FileInputStream对象:
        InputStream input = new FileInputStream("src/readme.txt");
        for (;;) {
        int n = input.read(); // 反复调用read()方法,直到返回-1
        if (n == -1) {
        break;
        }
        System.out.println(n); // 打印byte的值
        }
        input.close(); // 关闭流
        }
      • try ... finally来保证InputStream在无论是否发生IO错误的时候都能够正确地关闭

        • try(resource)

          1
          2
          3
          4
          5
          6
          7
          8
          public void readFile() throws IOException {
          try (InputStream input = new FileInputStream("src/readme.txt")) {
          int n;
          while ((n = input.read()) != -1) {
          System.out.println(n);
          }
          } // 编译器在此自动为我们写入finally并调用close()
          }
    • 缓冲

      • InputStream提供了两个重载方法来支持读取多个字节
        • int read(byte[] b):读取若干字节并填充到byte[]数组,返回读取的字节数
        • int read(byte[] b, int off, int len):指定byte[]数组的偏移量和最大填充数
    • 阻塞

      • 在调用InputStreamread()方法读取数据时,read()方法是阻塞Blocking

        1
        2
        3
        int n;
        n = input.read(); // 必须等待read()方法返回才能执行下一行代码
        int m = n;
    • InputStream实现类

      • FileInputStream从文件获取输入流
      • ByteArrayInputStream在内存中模拟一个InputStream
  • OutputStream

    • OutputStream
      • Java标准库提供的最基本的输出流

      • 是抽象类,是所有输出流的超类

      • 抽象类定义的一个最重要的方法就是void write(int b)

      • 方法签名

        1
        public abstract void write(int b) throws IOException;
      • 缓冲区、flush()

    • FileOutputStream
      • 一次性写入若干字节,OutputStream提供的重载方法void write(byte[])

        1
        2
        3
        4
        5
        public void writeFile() throws IOException {
        OutputStream output = new FileOutputStream("out/readme.txt");
        output.write("Hello".getBytes("UTF-8")); // Hello
        output.close();
        }
      • try(resource)来保证OutputStream在无论是否发生IO错误时都能正确地关闭

        1
        2
        3
        4
        5
        public void writeFile() throws IOException {
        try (OutputStream output = new FileOutputStream("out/readme.txt")) {
        output.write("Hello".getBytes("UTF-8")); // Hello
        } // 编译器在此自动为我们写入finally并调用close()
        }
      • 阻塞

        • InputStream一样,OutputStreamwrite()方法也是阻塞的
    • OutputStream实现类
      • FileOutputStream从文件获取输出流
      • ByteArrayOutputStream可以在内存中模拟一个OutputStream
  • **Filter**模式

    • 为了解决依赖继承会导致子类数量失控的问题,JDK将InputStream分为两大类
      • 一类是直接提供数据的基础InputStream,如:
        • FileInputStream
        • ByteArrayInputStream
        • ServletInputStream
      • 一类是提供额外附加功能的InputStream,如:
        • BufferedInputStream
        • DigestInputStream
        • CipherInputStream
      • 一个基础组件叠加各种附加功能组件的模式,称为Filter模式/装饰器模式Decorator
    • 编写FilterInputStream
  • 操作**Zip**

    • ZipInputStream是一种FilterInputStream,可以直接读取zip包的内容

    • 读取zip包

      • 创建一个ZipInputStream,通常是传入一个FileInputStream作为数据源,然后循环调用getNextEntry(),直到返回null,表示zip流结束

      • 一个ZipEntry表示一个压缩文件或目录,如果是压缩文件,我们就用read()方法不断读取,直到返回-1

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        try (ZipInputStream zip = new ZipInputStream(new FileInputStream(...))) {
        ZipEntry entry = null;
        while ((entry = zip.getNextEntry()) != null) {
        String name = entry.getName();
        if (!entry.isDirectory()) {
        int n;
        while ((n = zip.read()) != -1) {
        ...
        }
        }
        }
        }
    • 写入zip包

      • 创建一个ZipOutputStream,通常是包装一个FileOutputStream,然后,每写入一个文件前,先调用putNextEntry(),然后用write()写入byte[]数据,写入完毕后调用closeEntry()结束这个文件的打包

        1
        2
        3
        4
        5
        6
        7
        8
        try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(...))) {
        File[] files = ...
        for (File file : files) {
        zip.putNextEntry(new ZipEntry(file.getName()));
        zip.write(Files.readAllBytes(file.toPath()));
        zip.closeEntry();
        }
        }
  • 读取**classpath**资源

    • Java程序启动时需要从一个.properties文件中读取配置

    • 从classpath读取文件可以避免不同环境下文件路径不一致的问题

      • default.properties文件放到classpath中,就不用关心它的实际存放路径
    • 在classpath中的资源文件,路径总是以开头,我们先获取当前的Class对象,然后调用getResourceAsStream()就可以直接从classpath读取任意的资源文件

      1
      2
      3
      4
      5
      try (InputStream input = getClass().getResourceAsStream("/default.properties")) {
      if (input != null) {
      // TODO:
      }
      }
  • 序列化

    • 序列化

      • 是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组
      • 序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程
    • 反序列化

      • 把一个二进制内容(byte[]数组)变回Java对象
      • 保存到文件中的byte[]数组又可以变回Java对象,或者从网络上读取byte[]并把它变回Java对象
    • 序列化

      • 一个Java对象要能序列化,必须实现一个特殊的java.io.Serializable接口

        1
        public interface Serializable {...}
        • Serializable接口没有定义任何方法,是一个空接口,称为标记接口Marker Interface
      • 一个Java对象变为byte[]数组,需要使用ObjectOutputStream

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        import java.io.*;
        import java.util.Arrays;

        public class Main {
        public static void main(String[] args) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        try (ObjectOutputStream output = new ObjectOutputStream(buffer)) {
        // 写入int:
        output.writeInt(12345);
        // 写入String:
        output.writeUTF("Hello");
        // 写入Object,实现了Serializable接口的Object
        output.writeObject(Double.valueOf(123.456));
        }
        System.out.println(Arrays.toString(buffer.toByteArray()));
        }
        }
    • 反序列化

      • ObjectOutputStream相反,ObjectInputStream负责从一个字节流读取Java对象

        1
        2
        3
        4
        5
        try (ObjectInputStream input = new ObjectInputStream(...)) {
        int n = input.readInt();
        String s = input.readUTF();
        Double d = (Double) input.readObject();
        }
      • 除了能读取基本类型和String类型外,调用readObject()可以直接返回一个Object对象。要把它变成一个特定类型,必须强制转型。readObject()可能抛出的异常有:

        • ClassNotFoundException:没有找到对应的Class
        • InvalidClassException:Class不匹配
      • 反序列化时

        • 由JVM直接构造出Java对象
        • 不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行
  • Reader

    • java.io.Reader是所有字符输入流的超类,最主要的方法是

      1
      publicint read()throws IOException;
    • Reader是Java的IO库提供的另一个输入流接口

      • InputStream是一个字节流,即以byte为单位读取

      • Reader是一个字符流,即以char为单位读取

      • 对比

        InputStream Reader
        字节流,以byte为单位 字符流,以char为单位
        读取字节(-1,0~255):int read() 读取字符(-1,0~65535):int read()
        读到字节数组:int read(byte[] b) 读到字符数组:int read(char[] c)
    • FileReader

      • Reader的一个子类,它可以打开文件并获取Reader

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        public void readFile() throws IOException {
        // 创建一个FileReader对象:
        Reader reader = new FileReader("src/readme.txt", StandardCharsets.UTF_8);
        for (;;) {
        int n = reader.read(); // 反复调用read()方法,直到返回-1
        if (n == -1) {
        break;
        }
        System.out.println((char)n); // 打印char
        }
        reader.close(); // 关闭流
        }
        // 创建FileReader时指定编码,避免乱码
      • try (resource)来保证Reader在无论有没有IO错误的时候都能够正确地关闭

      • 一次性读取若干字符并填充到char[]数组

        1
        public int read(char[] c) throws IOException
        • 返回实际读入的字符个数,最大不超过char[]数组的长度
        • 返回-1表示流结束
    • CharArrayReader

      • CharArrayReader可以在内存中模拟一个Reader

      • 实际上是把一个char[]数组变成一个Reader,和ByteArrayInputStream类似

        1
        2
        try (Reader reader =new CharArrayReader("Hello".toCharArray())) {
        }
    • StringReader

      • StringReader可以直接把String作为数据源,它和CharArrayReader几乎一样

        1
        2
        try (Reader reader =new StringReader("Hello")) {
        }
    • InputStreamReader

      • 可以把任何InputStream转换为Reader

      • 构造InputStreamReader时,需要传入InputStream,需要指定编码,得到Reader对象

      • 通过try (resource)

        1
        2
        3
        try (Reader reader =new InputStreamReader(new FileInputStream("src/readme.txt"), "UTF-8")) {
        // TODO:
        }
      • 实际上就是FileReader的一种实现方式

      • 使用try (resource)结构时,当我们关闭Reader时,它会在内部自动调用InputStreamclose()方法,所以,只需要关闭最外层的Reader对象即可。

      • 使用InputStreamReader,可以把一个InputStream转换成一个Reader

  • Writer

    • Writer

      • Reader是带编码转换器的InputStream,把byte转换为char
      • Writer就是带编码转换器的OutputStream,把char转换为byte并输出
    • WriterOutputStream

      OutputStream Writer
      字节流,以byte为单位 字符流,以char为单位
      写入字节(0~255):void write(int b) 写入字符(0~65535):void write(int c)
      写入字节数组:void write(byte[] b) 写入字符数组:void write(char[] c)
      无对应方法 写入String:void write(String s)
    • Writer是所有字符输出流的超类

      • void write(int c)
      • void write(char[] c)
      • void write(String s)
    • FileWriter

      • FileWriter就是向文件中写入字符流的Writer。使用方法和FileReader类似

        1
        2
        3
        4
        5
        try (Writer writer =new FileWriter("readme.txt", StandardCharsets.UTF_8)) {
        writer.write('H');// 写入单个字符
        writer.write("Hello".toCharArray());// 写入char[]
        writer.write("Hello");// 写入String
        }
    • CharArrayWriter

      • CharArrayWriter可以在内存中创建一个Writer

      • 实际上是构造一个缓冲区,可以写入char,最后得到写入的char[]数组,和ByteArrayOutputStream非常类似

        1
        2
        3
        4
        5
        6
        try (CharArrayWriter writer =new CharArrayWriter()) {
        writer.write(65);
        writer.write(66);
        writer.write(67);
        char[] data = writer.toCharArray();// { 'A', 'B', 'C' }
        }
    • StringWriter

      • StringWriter也是一个基于内存的Writer,和CharArrayWriter类似
      • StringWriter在内部维护了一个StringBuffer,并对外提供了Writer接口
    • OutputStreamWriter

      • CharArrayWriterStringWriter外,普通Writer实际上是基于OutputStream构造的

      • 接收char,然后在内部自动转换成一个或多个byte,并写入OutputStream

      • 因此,OutputStreamWriter就是一个将任意的OutputStream转换为Writer的转换器

        1
        2
        3
        4
        try (Writer writer =new OutputStreamWriter(new FileOutputStream("readme.txt"), "UTF-8")) {
        // TODO:
        }

      • 上述代码实际上就是FileWriter的一种实现方式

  • PrintStreamPrintWriter

    • PrintStream
      • 一种FilterOutputStream,在OutputStream接口上,提供写入各种数据类型的方法
      • 写入intprint(int)
      • 写入booleanprint(boolean)
      • 写入Stringprint(String)
      • 写入Objectprint(Object),实际上相当于print(object.toString())
      • System.out是标准输出;
      • System.err是标准错误输出
    • PrintWriter
      • PrintStream最终输出是byte数据,PrintWriter则是扩展了Writer接口,它的print()/println()方法最终输出的是char数据
  • 使用**Files**

    • java.nio包里面的类
    • 对于简单的小文件读写操作,可以使用Files工具类

日期与时间

  • **Date & Time**
    • GMT或者UTC加时区偏移表示
      • 如:GMT+08:00或者UTC+08:00表示东八区
      • 本地化
        • Locale表示一个国家或地区的日期、时间、数字、货币等格式
        • Locale语言_国家的字母缩写构成
        • 如,zh_CN表示中文+中国,en_US表示英文+美国
  • Date & Calendar
    • Epoch Time

      • 时间戳,几种存储方式

        • 以秒为单位的整数
        • 以毫秒为单位的整数
        • 以秒为单位的浮点数
      • 通常是用long表示的毫秒数,即:

        1
        long t = 1574208900123L;
      • 获取当前时间戳,使用System.currentTimeMillis()

    • 标准库API

      • java.utilpackage
        • 主要包括DateCalendarTimeZone
      • java.timepackage
        • 主要包括LocalDateTimeZonedDateTimeZoneId
    • Date

      • java.util.Date是用于表示一个日期和时间的对象
      • 用法示例
      • Date对象
        • 不能转换时区,除了toGMTString()可以按GMT+0:00输出外
        • 很难对日期和时间进行加减
    • Calendar

      • 用于获取并设置年、月、日、时、分、秒
      • 只有一种方式获取即Calendar.getInstance(),而且获取到是当前时间
      • Calendar.getTime()可以将一个Calendar对象转换成Date对象
    • TimeZone

      • CalendarDate相比,它提供了时区转换的功能
  • LocalDateTime
    • java.time包提供了新的日期和时间API
      • 本地日期和时间:LocalDateTimeLocalDateLocalTime
      • 带时区的日期和时间:ZonedDateTime
      • 时刻:Instant
      • 时区:ZoneIdZoneOffset
      • 时间间隔:Duration
      • 格式化类型:DateTimeFormatter
    • LocalDateTime、DateTimeFormatter、Duration和Period
      • Duration表示两个时刻之间的时间间隔、Period表示两个日期之间的天数
      • LocalDateTime可以非常方便地对日期和时间进行加减,或者调整日期和时间,它总是返回新对象
      • 使用isBefore()isAfter()可以判断日期和时间的先后
      • 使用DurationPeriod可以表示两个日期和时间的“区间间隔”
  • ZonedDateTime
    • 带时区的日期和时间
      • LocalDateTimeZoneId
    • 时区转换
      • ZonedDateTime对象
      • withZoneSameInstant()将关联时区转换到另一个时区
  • DateTimeFormatter
    • 不变对象、线程安全
    • ZonedDateTimeLocalDateTime进行格式化,需要使用DateTimeFormatter
    • DateTimeFormatter可以通过格式化字符串和Locale对日期和时间进行定制输出
  • Instant
    • System.currentTimeMillis()返回的就是以毫秒表示的当前时间戳
      • java.time中以Instant类型表示,用Instant.now()获取当前时间戳
    • Instant表示高精度时间戳,它可以和ZonedDateTime以及long互相转换

单元测试

  • 编写**JUnit**测试

    • 单元测试代码本身必须非常简单,能一下看明白,决不能再为测试代码编写测试

    • 每个单元测试应当互相独立,不依赖运行的顺序

    • 测试时不但要覆盖常用测试用例,还要特别注意测试边界条件,例如输入为0null,空字符串""等情况

    • FactorialTest.java

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      package com.itranswarp.learnjava;

      import static org.junit.jupiter.api.Assertions.*;
      import org.junit.jupiter.api.Test;

      public class FactorialTest {

      @Test
      void testFact() {
      assertEquals(1, Factorial.fact(1));
      assertEquals(2, Factorial.fact(2));
      assertEquals(6, Factorial.fact(3));
      assertEquals(3628800, Factorial.fact(10));
      assertEquals(2432902008176640000L, Factorial.fact(20));
      }
      }
  • 使用Fixture

    • 编写测试前准备、测试后清理的固定代码
      • 对于实例变量,在@BeforeEach中初始化,在@AfterEach中清理
        • 在各个@Test方法中互不影响,因为是不同的实例
      • 对于静态变量,在@BeforeAll中初始化,在@AfterAll中清理
        • 在各个@Test方法中均是唯一实例,会影响各个@Test方法
  • 异常测试

    • assertThrows()期望捕获一个指定异常、第二个参数封装了要执行的会产生异常的代码

      1
      2
      3
      4
      5
      6
      @Test
      void testNegative() {
      assertThrows(IllegalArgumentException.class, () -> {
      Factorial.fact(-1);
      });
      }
  • 条件测试

    • 根据某些注解在运行期让JUnit自动忽略某些测试
  • 参数化测试

    • @ParameterizedTest注解,进行参数化测试
    • 参数传入
      • @MethodSource
        • 编写一个同名的静态方法来提供测试参数
      • @CsvSource
        • 每一个字符串表示一行
      • @CsvFileSource

正则表达式

  • 正则表达式

    • java.util.regex
    • 用字符串来描述规则,并用来匹配字符串
  • 匹配规则

    • 单字符

      正则表达式 规则 可以匹配
      A 指定字符 A
      \u548c 指定Unicode字符
      . 任意字符 a,b,&,0
      \d 数字0~9 0~9
      \w 大小写字母,数字和下划线 az,AZ,0~9,_
      \s 空格、Tab键 空格,Tab
      \D 非数字 a,A,&,_,……
      \W 非\w &,@,中,……
      \S 非\s a,A,&,_,……
    • 多个字符

      正则表达式 规则 可以匹配
      A* 任意个数字符 空,A,AA,AAA,……
      A+ 至少1个字符 A,AA,AAA,……
      A? 0个或1个字符 空,A
      A{3} 指定个数字符 AAA
      A{2,3} 指定范围个数字符 AA,AAA
      A{2,} 至少n个字符 AA,AAA,AAAA,……
      A{0,3} 最多n个字符 空,A,AA,AAA
  • 复杂匹配规则

    • 匹配开头和结尾
      • ^表示开头,$表示结尾
    • 匹配指定范围
      • [...]匹配范围内的字符
        • [1-9]
        • [0-9a-fA-F] 、六位[0-9a-fA-F]{6}
      • 不包含指定范围的字符
        • [^1-9]{3}
    • 或规则匹配
      • |连接的两个正则规则是或规则
    • 使用括号
      • 公共部分提出来,(...)把子规则括起来成learn\\s(java|php|go)
  • 分组匹配

    • 引入java.util.regex
      • (...)先分组,用Pattern对象匹配
      • 匹配后获得一个Matcher对象
      • 如果匹配成功,从Matcher.group(index)返回子串
      • Matcher.group(index)方法的参数
        • 0即整个正则匹配到的字符串

        • 1表示第一个子串,2表示第二个子串

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          import java.util.regex.*;

          public class Main {
          public static void main(String[] args) {
          Pattern pattern = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
          pattern.matcher("010-12345678").matches(); // true
          pattern.matcher("021-123456").matches(); // false
          pattern.matcher("022#1234567").matches(); // false
          // 获得Matcher对象:
          Matcher matcher = pattern.matcher("010-12345678");
          if (matcher.matches()) {
          String whole = matcher.group(0); // "010-12345678", 0表示匹配的整个字符串
          String area = matcher.group(1); // "010", 1表示匹配的第1个子串
          String tel = matcher.group(2); // "12345678", 2表示匹配的第2个子串
          System.out.println(area);
          System.out.println(tel);
          }
          }
          }
  • 非贪婪匹配

    • 正则表达式匹配默认贪婪匹配,使用?表示对某一规则进行非贪婪匹配
  • 搜索和替换

    • 分割字符串

      • String.split()方法传入正则表达式
    • 搜索字符串

      • 获取到Matcher对象后,反复调用find()方法,搜索能匹配上\\wo\\w规则的子串

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        public class Main {
        public static void main(String[] args) {
        String s = "the quick brown fox jumps over the lazy dog.";
        Pattern p = Pattern.compile("\\wo\\w");
        Matcher m = p.matcher(s);
        while (m.find()) {
        String sub = s.substring(m.start(), m.end());
        System.out.println(sub);
        }
        }
        }
    • 替换字符串

      • String.replaceAll()(正则表达式,待替换的字符串)
    • 反向引用

      • 把任何4字符单词的前后用<b>xxxx</b>括起来

        • " <b>$1</b> "用匹配的分组子串([a-z]{4})替换了$1
        1
        2
        3
        4
        5
        6
        7
        8
        public class Main {
        public static void main(String[] args) {
        String s = "the quick brown fox jumps over the lazy dog.";
        String r = s.replaceAll("\\s([a-z]{4})\\s", " <b>$1</b> ");
        System.out.println(r);
        }
        }
        // the quick brown fox jumps <b>over</b> the <b>lazy</b> dog.

多线程

  • 多线程基础

    • 进程
      • 一个任务称为一个进程
      • 子任务称为线程
      • 一个进程可以包含一个或多个线程,但至少会有一个线程
  • 创建新线程

    • 实例化一个Thread实例,调用start()方法

      • 一个线程对象只能调用一次start()方法

        • 线程的执行代码写在run()方法中
        • 线程调度由操作系统决定,程序本身无法决定调度顺序
        • Thread.sleep()可以把当前线程暂停一段时间
      • Thread派生一个自定义类,然后覆写run()方法

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        public class Main {
        public static void main(String[] args) {
        Thread t = new MyThread();
        t.start(); // 启动新线程
        }
        }

        class MyThread extends Thread {
        @Override
        public void run() {
        System.out.println("start new thread!");
        }
        }
      • 创建Thread实例时,传入一个Runnable实例

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        public class Main {
        public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start(); // 启动新线程
        }
        }

        class MyRunnable implements Runnable {
        @Override
        public void run() {
        System.out.println("start new thread!");
        }
        }
      • lambda

        1
        2
        3
        4
        5
        6
        7
        8
        public class Main {
        public static void main(String[] args) {
        Thread t = new Thread(() -> {
        System.out.println("start new thread!");
        });
        t.start(); // 启动新线程
        }
        }
    • 对线程设定优先级

      1
      Thread.setPriority(int n)// 1~10, 默认值5
  • 线程状态

    • 线程状态
      • New:新创建的线程,尚未执行
      • Runnable:运行中的线程,正在执行run()方法的Java代码
      • Blocked:运行中的线程,因为某些操作被阻塞而挂起
      • Waiting:运行中的线程,因为某些操作在等待中
      • Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待
      • Terminated:线程已终止,因为run()方法执行完毕
    • 线程终止的原因有:
      • 线程正常终止:run()方法执行到return语句返回
      • 线程意外终止:run()方法因为未捕获的异常导致线程终止
      • 对某个线程的Thread实例调用stop()方法强制终止
    • 一个线程还可以等待另一个线程直到其运行结束
      • main线程在启动t线程后,通过t.join()等待t线程结束后再继续运行
      • 可以指定等待时间,超过等待时间线程仍然没有结束就不再等待
      • 对已经运行结束的线程调用join()方法会立刻返回
  • 中断线程

    • 在其他线程中对目标线程调用interrupt()方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行
    • running标志位来标识线程是否应该继续运行
    • HelloThread的标志位boolean running是一个线程间共享的变量
      • 线程间共享变量使用volatile关键字标记,确保每个线程都能读取到更新后的变量值

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        public class Main {
        public static void main(String[] args) throws InterruptedException {
        HelloThread t = new HelloThread();
        t.start();
        Thread.sleep(1);
        t.running = false; // 标志位置为false
        }
        }

        class HelloThread extends Thread {
        public volatile boolean running = true;
        public void run() {
        int n = 0;
        while (running) {
        n ++;
        System.out.println(n + " hello!");
        }
        System.out.println("end!");
        }
        }
      • volatile关键字

        • 每次访问变量时,总是获取主内存的最新值
        • 每次修改变量后,立刻回写到主内存
  • 守护线程

    • 守护线程(Daemon Thread)
      • 守护线程是指为其他线程服务的线程
      • 调用start()方法前,调用setDaemon(true)把该线程标记为守护线程
      • 守护线程不能持有任何需要关闭的资源
  • 线程同步

    • 通过加锁和解锁的操作,就能保证3条指令总是在一个线程执行期间,不会有其他线程会进入此指令区间

    • 加锁和解锁之间的代码块我们称之为临界区(Critical Section),任何时候临界区最多只有一个线程能执行

    • synchronized关键字对一个对象进行加锁

      1
      2
      3
      synchronized(lock) {
      n = n + 1;
      }
      • 找出修改共享变量的线程代码块
      • 选择一个共享实例作为锁
      • 使用synchronized(lockObject) { ... }
    • 无论是否有异常,都会在synchronized结束处正确释放锁

    • 不需要synchronized的操作

      • JVM规范定义了几种原子操作
        • 基本类型(longdouble除外)赋值,如:int n = m
        • 引用类型赋值,如:List<String> list = anotherList
    • 多行赋值语句,就必须保证是同步操作

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      classPoint {int x;
      int y;
      publicvoid set(int x,int y) {
      synchronized(this) {
      this.x = x;
      this.y = y;
      }
      }
      }

      • 写需要同步,读也需要同步
    • 不可变对象无需同步

  • 同步方法

    • synchronized逻辑封装

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public class Counter {
      private int count = 0;

      public void add(int n) {
      synchronized(this) {
      count += n;
      }
      }

      public void dec(int n) {
      synchronized(this) {
      count -= n;
      }
      }

      public int get() {
      return count;
      }
      }
      // synchronized锁住的对象是this,即当前实例,使得创建多个Counter实例,互不影响,可以并发执行
    • 一个类被设计为允许多线程正确访问,我们就说这个类就是线程安全thread-safe

      • java.lang.StringBuffer
      • 不变类,例如StringIntegerLocalDate
      • 类似Math这些只提供静态方法,没有成员变量的类
    • 锁住this实例=用synchronized修饰这个方法

      • synchronized修饰的方法就是同步方法,表示整个方法都必须用this实例加锁

        1
        2
        3
        4
        5
        6
        7
        8
        9
        public void add(int n) {
        synchronized(this) { // 锁住this
        count += n;
        } // 解锁
        }

        public synchronized void add(int n) { // 锁住this
        count += n;
        } // 解锁
    • static方法添加synchronized,锁住的是该类的Class实例

      1
      2
      3
      4
      5
      6
      publicclassCounter {publicstaticvoid test(int n) {
      synchronized(Counter.class) {
      ...
      }
      }
      }
  • 死锁

    • 可重入锁
      • 同一个线程重复获取同一个锁,这种能被同一个线程反复获取的锁
      • 每获取一次锁记录+1,每退出synchronized块记录-1,减到0时释放锁
    • 死锁
      • 两个线程各自持有不同的锁,各自试图获取对方的锁,造成了双方无限等待下去
      • 死锁发生后,只能强制结束JVM进程
      • 线程获取锁的顺序要一致
  • **wait****notify**

    • wait

      • 线程协调运行

        • 当条件不满足时,线程进入等待状态
        • 当条件满足时,线程被唤醒继续执行任务
      • wait()方法必须在当前获取的锁对象上调用,这里获取的是this

        1
        2
        3
        4
        5
        6
        7
        8
        public synchronized String getTask() {
        while (queue.isEmpty()) {
        // 释放this锁:
        this.wait();
        // 重新获取this锁
        }
        return queue.remove();
        }
    • notify

      • wait()方法返回时需要重新获得this

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        public class Main {
        public static void main(String[] args) throws InterruptedException {
        var q = new TaskQueue();
        var ts = new ArrayList<Thread>();
        for (int i=0; i<5; i++) {
        var t = new Thread() {
        public void run() {
        // 执行task:
        while (true) {
        try {
        String s = q.getTask();
        System.out.println("execute task: " + s);
        } catch (InterruptedException e) {
        return;
        }
        }
        }
        };
        t.start();
        ts.add(t);
        }
        var add = new Thread(() -> {
        for (int i=0; i<10; i++) {
        // 放入task:
        String s = "t-" + Math.random();
        System.out.println("add task: " + s);
        q.addTask(s);
        try { Thread.sleep(100); } catch(InterruptedException e) {}
        }
        });
        add.start();
        add.join();
        Thread.sleep(100);
        for (var t : ts) {
        t.interrupt();
        }
        }
        }

        class TaskQueue {
        Queue<String> queue = new LinkedList<>();

        public synchronized void addTask(String s) {
        this.queue.add(s);
        this.notifyAll();
        }

        public synchronized String getTask() throws InterruptedException {
        while (queue.isEmpty()) {
        this.wait();
        }
        return queue.remove();
        }
        }
  • ReentrantLock

    • java.util.concurrent.locks
      • 提供的ReentrantLock用于替代synchronized加锁
      • ReentrantLock是可重入锁
        • synchronized一样,一个线程可以多次获取同一个锁
        • synchronized不同的是,ReentrantLock可以尝试获取锁
  • Condition

    • Condition对象
      • 来实现ReentrantLockwaitnotify

      • 使用Condition

        • 引用的Condition对象必须从Lock实例的newCondition()返回,获得一个绑定Lock实例的Condition实例
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        class TaskQueue {
        private final Lock lock = new ReentrantLock();
        private final Condition condition = lock.newCondition();
        private Queue<String> queue = new LinkedList<>();

        public void addTask(String s) {
        lock.lock();
        try {
        queue.add(s);
        condition.signalAll();
        } finally {
        lock.unlock();
        }
        }

        public String getTask() {
        lock.lock();
        try {
        while (queue.isEmpty()) {
        condition.await();
        }
        return queue.remove();
        } finally {
        lock.unlock();
        }
        }
        }
      • Condition提供的await()signal()signalAll()

        • synchronized锁对象的wait()notify()notifyAll()
        • await()会释放当前锁,进入等待状态
        • signal()会唤醒某个等待线程
        • signalAll()会唤醒所有等待线程
        • 唤醒线程从await()返回后需要重新获得锁
  • ReadWriteLock

    • ReadWriteLock
    • 只允许一个线程写入(其他线程既不能写入也不能读取)
    • 没有写入时,多个线程允许同时读(提高性能)
  • StampedLock

    • 读的过程中也允许获取写锁后写入
  • Semaphore

    • 保证同一时间最多N个线程访问受限资源
  • **Concurrent**集合

    • java.util.concurrent包也提供了对应的并发集合类

      interface non-thread-safe thread-safe
      List ArrayList CopyOnWriteArrayList
      Map HashMap ConcurrentHashMap
      Set HashSet / TreeSet CopyOnWriteArraySet
      Queue ArrayDeque / LinkedList ArrayBlockingQueue / LinkedBlockingQueue
      Deque ArrayDeque / LinkedList LinkedBlockingDeque
  • Atomic

    • Atomic类是通过无锁(lock-free)的方式实现的线程安全(thread-safe)访问
    • java.util.concurrent.atomic提供的原子操作可以简化多线程编程
      • 原子操作实现了无锁的线程安全
      • 适用于计数器,累加器等
  • 线程池

    • 线程池:接收大量小任务并进行分发处理

      • ExecutorService接口表示线程池

        1
        2
        3
        4
        5
        6
        7
        8
        // 创建固定大小的线程池:
        ExecutorService executor = Executors.newFixedThreadPool(3);
        // 提交任务:
        executor.submit(task1);
        executor.submit(task2);
        executor.submit(task3);
        executor.submit(task4);
        executor.submit(task5);
      • 常用实现类

        • FixedThreadPool:线程数固定的线程池
        • CachedThreadPool:线程数根据任务动态调整的线程池
        • SingleThreadExecutor:仅单线程执行的线程池
    • ScheduledThreadPool

      • 定期反复执行
  • **Future**

    • 对线程池提交一个Callable任务,可以获得一个Future对象
    • 可以用Future在将来某个时刻获取结果
  • **CompletableFuture**

    • 可以指定异步处理流程
      • thenAccept()处理正常结果
      • exceptional()处理异常结果
      • thenApplyAsync()用于串行化另一个CompletableFuture
      • anyOf()allOf()用于并行化多个CompletableFuture
  • ForkJoin

    • Fork/Join线程池:把一个大任务拆成多个小任务并行执行
    • 基于“分治”的算法:通过分解任务,并行执行,最后合并结果得到最终结果
    • ForkJoinPool线程池可以把一个大任务分拆成小任务并行执行,任务类必须继承自RecursiveTaskRecursiveAction
    • 使用Fork/Join模式可以进行并行计算以提高效率
  • ThreadLocal

    • ThreadLocal
      • 表示线程的“局部变量”,确保每个线程的ThreadLocal变量都是各自独立的
      • 适合在一个线程的处理流程中保持上下文(避免同一参数在所有方法中传递)
      • 使用ThreadLocal要用try ... finally结构,并在finally中清除
  • 虚拟线程

    • 为了解决IO密集型任务的吞吐量,它可以高效通过少数线程去调度大量虚拟线程

函数式编程

  • **Lambda**基础
    • 函数式编程

      • Functional Programming把函数作为基本运算单元、变量,可以接收函数、返回函数
    • Lambda表达式

      • 调用Arrays.sort()时传入一个Comparator实例

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        import java.util.Arrays;

        public class Main {
        public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, (s1, s2) -> {
        return s1.compareTo(s2);
        });
        System.out.println(String.join(", ", array));
        }
        }
      • Lambda表达式

        1
        2
        3
        (s1, s2) -> {
        return s1.compareTo(s2);
        }
        • 参数是(s1, s2)
        • -> { ... }表示方法体
        • 参数类型可以省略,编译器自动推断出String类型
      • 单方法接口,即一个接口只定义了一个方法,如Comparator

      • 定义了单方法的接口称之为FunctionalInterface,用注解@FunctionalInterface标记

      • Callable接口

        1
        2
        3
        4
        @FunctionalInterface
        public interfaceCallable<V> {
        V call()throws Exception;
        }
      • Comparator接口

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        @FunctionalInterface
        public interface Comparator<T> {

        int compare(T o1, T o2);

        boolean equals(Object obj);

        default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
        }

        default Comparator<T> thenComparing(Comparator<? super T> other) {
        ...
        }
        ...
        }
        • Comparator接口只有一个抽象方法int compare(T o1, T o2)
        • 其他的方法都是default方法或static方法
        • boolean equals(Object obj)Object定义的方法,不算在接口方法内
        • 因此,Comparator也是一个FunctionalInterface
  • 方法引用
    • 指如果某个方法签名和接口恰好一致,就可以直接传入方法引用

      • Arrays.sort()中直接传入了静态方法cmp的引用,用Main::cmp表示

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        import java.util.Arrays;

        public class Main {
        public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, Main::cmp);
        System.out.println(String.join(", ", array));
        }

        static int cmp(String s1, String s2) {
        return s1.compareTo(s2);
        }
        }
      • 引用实例方法

        1
        2
        3
        4
        5
        6
        7
        8
        9
        import java.util.Arrays;

        public class Main {
        public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, String::compareTo);
        System.out.println(String.join(", ", array));
        }
        }
        • 其中,String.compareTo()实例方法

          1
          2
          3
          4
          5
          6
          7
          public final class String {
          public int compareTo(String o) {
          ...
          }
          }
          // String类的compareTo()方法在实际调用的时候,第一个隐含参数总是传入this,相当于静态方法:
          public static int compareTo(String this, String o);
      • 引用构造方法

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        import java.util.*;
        import java.util.stream.*;

        public class Main {
        public static void main(String[] args) {
        List<String> names = List.of("Bob", "Alice", "Tim");
        List<Person> persons = names.stream().map(Person::new).collect(Collectors.toList());
        System.out.println(persons);
        }
        }

        class Person {
        String name;
        public Person(String name) {
        this.name = name;
        }
        public String toString() {
        return "Person:" + this.name;
        }
        }
    • FunctionalInterface

      • 不强制继承关系,不需要方法名称相同
      • 方法参数(类型和数量)与方法返回类型相同,即认为方法签名相同
  • 使用**Stream**
    • Stream API

      • 位于java.util.stream包,代表的是任意Java对象的序列

      • 不同于java.io

        java.io java.util.stream
        存储 顺序读写的byte或char
        用途 序列化至文件或网络
      • List是操作一组已存在的Java对象,而Stream实现的是惰性计算

        java.util.List java.util.stream
        元素 已分配并存储在内存 可能未分配,实时计算
        用途 操作一组已存在的Java对象 惰性计算
      • 可以“存储”有限个或无限个元素

      • 转换为另一个Stream,而不是修改原Stream本身(只存储了转换规则)

      • 惰性计算

        1
        2
        3
        4
        Stream<BigInteger> naturals = createNaturalStream(); // 不计算
        Stream<BigInteger> s2 = naturals.map(BigInteger::multiply); // 不计算
        Stream<BigInteger> s3 = s2.limit(100); // 不计算
        s3.forEach(System.out::println); // 计算
      • 链式

        1
        2
        3
        4
        5
        int result = createNaturalStream() // 创建Stream
        .filter(n -> n % 2 == 0) // 任意个转换
        .map(n -> n * n) // 任意个转换
        .limit(100) // 任意个转换
        .sum(); // 最终计算结果
    • 创建**Stream**

      • Stream.of()

        Stream<String> stream = Stream.of("A", "B", "C", "D");

      • 基于数组或Collection

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        import java.util.*;
        import java.util.stream.*;

        public class Main {
        public static void main(String[] args) {
        // 数组Arrays.stream()
        Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" });
        //Collection调用stream()
        Stream<String> stream2 = List.of("X", "Y", "Z").stream();
        stream1.forEach(System.out::println);
        stream2.forEach(System.out::println);
        }
        }
      • 基于Supplier

        • Stream.generate()方法,传入一个Supplier对象

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          import java.util.function.*;
          import java.util.stream.*;

          public class Main {
          public static void main(String[] args) {
          Stream<Integer> natual = Stream.generate(new NatualSupplier());
          // 无限序列必须先变成有限序列再打印
          natual.limit(20).forEach(System.out::println);
          }
          }

          class NatualSupplier implements Supplier<Integer> {
          int n = 0;
          public Integer get() {
          n++;
          return n;
          }
          }
      • 其他

        • Files类的lines()方法把一个文件变成一个Stream,每个元素代表文件一行内容
        • 正则表达式的Pattern对象有一个splitAsStream()方法,把一个长字符串分割成Stream序列而不是数组
      • 基本类型

        • IntStreamLongStreamDoubleStream
          • 使用基本类型的Stream,目的是提高运行效率

            1
            2
            3
            4
            // 将int[]数组变为IntStream:
            IntStream is = Arrays.stream(newint[] { 1, 2, 3 });
            // 将Stream<String>转换为LongStream:
            LongStream ls = List.of("1", "2", "3").stream().mapToLong(Long::parseLong);
    • 使用**map**

      • Stream.map()

        • map()方法接收的对象是Function接口对象

          • 它定义了一个apply()方法,负责把一个T类型转换成R类型:

            1
            <R> Stream<R> map(Function<? super T, ? extends R> mapper);
          • 其中,Function的定义

            1
            2
            3
            4
            @FunctionalInterface
            publicinterfaceFunction<T,R> {// 将T类型转换为R:
            R apply(T t);
            }
        • 数学计算、字符串操作、任何Java对象

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          import java.util.*;
          import java.util.stream.*;

          public class Main {
          public static void main(String[] args) {
          List.of(" Apple ", " pear ", " ORANGE", " BaNaNa ")
          .stream()
          .map(String::trim) // 去空格
          .map(String::toLowerCase) // 变小写
          .forEach(System.out::println); // 打印
          }
          }
      • Stream.filter()

        • filter()方法接收的对象是Predicate接口对象,它定义了一个test()方法,负责判断元素是否符合条件

          1
          2
          3
          @FunctionalInterface
          publicinterfacePredicate<T> {// 判断元素t是否符合条件:boolean test(T t);
          }
        • 常用于数值、任何Java对象

          • 从一组给定的LocalDate中过滤掉工作日,以便得到休息日
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          import java.time.*;
          import java.util.function.*;
          import java.util.stream.*;

          public class Main {
          public static void main(String[] args) {
          Stream.generate(new LocalDateSupplier())
          .limit(31)
          .filter(ldt -> ldt.getDayOfWeek() == DayOfWeek.SATURDAY || ldt.getDayOfWeek() == DayOfWeek.SUNDAY)
          .forEach(System.out::println);
          }
          }

          class LocalDateSupplier implements Supplier<LocalDate> {
          LocalDate start = LocalDate.of(2020, 1, 1);
          int n = -1;
          public LocalDate get() {
          n++;
          return start.plusDays(n);
          }
          }
    • 使用**reduce**

      • Stream.reduce()
        • map()filter()都是Stream的转换方法

        • Stream.reduce()则是Stream的一个聚合方法,把一个Stream的所有元素按照聚合函数聚合成一个结果

        • 聚合方法会立刻对Stream进行计算,对一个Stream做聚合计算后,结果就不是一个Stream,而是一个其他的Java对象

        • reduce()方法传入的对象是BinaryOperator接口,它定义了一个apply()方法,负责把上次累加的结果和本次的元素进行运算,并返回累加的结果

          1
          2
          3
          4
          @FunctionalInterface
          publicinterfaceBinaryOperator<T> {// Bi操作:两个输入,一个输出
          T apply(T t, T u);
          }
    • 输出集合

      • 输出为List

        • 调用collect()并传入Collectors.toList()对象
          • 实际上是一个Collector实例,通过类似reduce()的操作,把每个元素添加到一个收集器中(实际上是ArrayList

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            import java.util.*;
            import java.util.stream.*;

            public class Main {
            public static void main(String[] args) {
            Stream<String> stream = Stream.of("Apple", "", null, "Pear", " ", "Orange");
            List<String> list = stream.filter(s -> s != null && !s.isBlank()).collect(Collectors.toList());
            System.out.println(list);
            }
            }
      • 输出为数组

        • 调用toArray()方法,并传入数组的“构造方法”

          1
          2
          3
          4
          List<String> list = List.of("Apple", "Banana", "Orange");
          String[] array = list.stream().toArray(String[]::new);
          // 构造方法是String[]::new,签名实际上是IntFunction<String[]>定义的String[] apply(int)
          // 即传入int参数,获得String[]数组的返回值
      • 输出为Map

        • 指定两个映射函数,分别把元素映射为key和value

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          import java.util.*;
          import java.util.stream.*;

          public class Main {
          public static void main(String[] args) {
          Stream<String> stream = Stream.of("APPL:Apple", "MSFT:Microsoft");
          Map<String, String> map = stream
          .collect(Collectors.toMap(
          // 把元素s映射为key:
          s -> s.substring(0, s.indexOf(':')),
          // 把元素s映射为value:
          s -> s.substring(s.indexOf(':') + 1)));
          System.out.println(map);
          }
          }
      • 分组输出

        • Collectors.groupingBy()提供两个函数

          • 一分组的key,这里使用s -> s.substring(0, 1),表示只要首字母相同的String分到一组
          • 分组的value,这里直接使用Collectors.toList(),表示输出为List
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          import java.util.*;
          import java.util.stream.*;

          public class Main {
          public static void main(String[] args) {
          List<String> list = List.of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots");
          Map<String, List<String>> groups = list.stream()
          .collect(Collectors.groupingBy(s -> s.substring(0, 1), Collectors.toList()));
          System.out.println(groups);
          }
          }
    • 其他

      • 排序

        • 调用sorted()方法,要求Stream的每个元素必须实现Comparable接口

        • 如果要自定义排序,传入指定的Comparator

          1
          2
          3
          4
          List<String> list = List.of("Orange", "apple", "Banana")
          .stream()
          .sorted(String::compareToIgnoreCase)
          .collect(Collectors.toList());
        • sorted()只是一个转换操作,它会返回一个新的Stream

      • 去重

        • distinct()

          1
          2
          3
          4
          List.of("A", "B", "A", "C", "B", "D")
          .stream()
          .distinct()
          .collect(Collectors.toList());// [A, B, C, D]
      • 截取

        • 截取操作常用于把一个无限的Stream转换成有限的Stream
          • skip()用于跳过当前Stream的前N个元素

          • limit()用于截取当前Stream最多前N个元素

            1
            2
            3
            4
            5
            List.of("A", "B", "C", "D", "E", "F")
            .stream()
            .skip(2)// 跳过A, B
            .limit(3)// 截取C, D, E
            .collect(Collectors.toList());// [C, D, E]
          • 截取操作也是一个转换操作,将返回新的Stream

      • 合并

        • 将两个Stream合并为一个Stream使用Stream静态方法concat()

          1
          2
          3
          4
          5
          Stream<String> s1 = List.of("A", "B", "C").stream();
          Stream<String> s2 = List.of("D", "E").stream();
          // 合并:
          Stream<String> s = Stream.concat(s1, s2);
          System.out.println(s.collect(Collectors.toList())); // [A, B, C, D, E]
      • flatMap

        • Stream转换为Stream<Integer>,使用flatMap()

          1
          2
          Stream<Integer> i = s.flatMap(list -> list.stream());

        • Stream的每个元素(这里是List)映射为Stream,合并成一个新Stream

      • 并行

        1
        2
        3
        4
        Stream<String> s = ...
        String[] result = s.parallel() // 变成一个可以并行处理的Stream
        .sorted() // 可以进行并行排序
        .toArray(String[]::new);
      • 其他聚合方法

        • 除了reduce()collect()
          • count():用于返回元素个数
          • max(Comparator<? super T> cp):找出最大元素
          • min(Comparator<? super T> cp):找出最小元素
        • 针对IntStreamLongStreamDoubleStream
          • sum():对所有元素求和
          • average():对所有元素求平均数
        • 用来测试Stream的元素是否满足以下条件
          • boolean allMatch(Predicate<? super T>):是否所有元素均满足测试条件
          • boolean anyMatch(Predicate<? super T>):是否至少一个元素满足测试条件
        • forEach()
          • 循环处理Stream的每个元素,System.out::println打印Stream的元素
  • Title: Java
  • Author: Murphy Lee
  • Created at : 2023-07-01 10:01:27
  • Updated at : 2023-12-29 15:18:46
  • Link: https://redefine.ohevan.com/2023/07/01/Java/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments