JDK8的新特性

一、JDK8的新特性(2014-2023长支持版本)

1.1 Lambda表达式

1.1.1 什么是lambda表达式

lambda是对基于接口的匿名内部类编写方式的一种优化,使的它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java语言表达能力得到进一步提升。

1.1.2 基本案例
//原始写法
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("这是一个线程!");
    }
}).start();
//Lambda表达式的写法
new Thread(() -> System.out.println("这是一个线程")).start();
1.1.3 语法格式
(参数1,参数2,...) -> {
    方法体;
    return 返回值;
}
  • 参数部分:与接口中方法的形参数量保持一致
  • 方法体部分:方法的具体具体实现

注意

  • 参数类型可以省略不写,Lambda可以自动类型推导
  • 如果只有一个参数的情况下,小括号可以省略不写
  • 如果方法体只有一条语句时,大括号可以省略不写
  • 如果方法体中只有一条返回语句时,大括号和return关键字都可以省略不写
  • 在普通的匿名内部类中,this代表的是当前匿名内部类的对象, 而Lambda表达式中,this代表的是当前类对象
1.1.4 函数式接口

虽然Lambda表达式可以简化接口的实现,但是并不是所有的接口都可以使用Lambda表达式来简化开发的。Lambda表达式只能实现函数式接口

什么是函数式接口?

简单说,一个接口中有且只有一个抽象方法时,这个接口就是函数式接口

@FunctionalInterface
public interface A {
    void method(String a);
}

可以使用注解**@Functionalnterface**来判断一个接口是否为函数式接口

通用的函数式接口


/**
参数 - T
返回值 - void
作用 - 对类型T的参数进行相关操作处理
*/
@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

}

供给型接口

/**
参数 - 无
返回值 - T
作用 - 返回类型T的数据
*/
@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

函数型接口

/**
参数 - T
返回值 - R
作用 - 对参数T进行处理,并返回处理结果R
*/
@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

断言型接口

/**
参数 - T
返回值 - Boolean
作用 - 根据参数T,进行某种逻辑判断,并返回判断的结果
*/
@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}
1.1.5 方法引用

方法引用就是通过一种特定的形式,进行方法调用。 本质上是为了进一步简化Lambda表达式的写法而诞生的

public static void main(String[] args) {
    //方法引用之前
    method(a -> System.out.println(a));
    
    //方法引用之后
    method(System.out::println);
}

//同级的静态方法
public static void method(Consumer consumer){
    consumer.accept("Hello");
}

方法引用的语法

  • (函数式)接口类型 对象变量名 = 对象::被引用的实例方法;
  • (函数式)接口类型 对象变量名 = 类名::被引用的静态方法;
  • (函数式)接口类型 对象变量名 = 类名::被引用的实例方法;
  • (函数式)接口类型 对象变量名 = 类名::new(引用的是类的构造方法);

1.2 interface的新特性

JDK1.8之后,接口(interface)中允许编写 静态方法和默认方法

public interface InterfaceDemo {

    /**
     * 常亮 - 默认加上 public static final
     */
    int a = 10;

    /**
     * 抽象方法 - 默认加上 public abstract
     */
    void method1();

    /**
     * 静态方法 - 默认加上public
     */
    static void method2(){
        System.out.println("静态实现...");
    }

    /**
     * 默认方法 - 默认加上 public
     */
    default void method3(){
        System.out.println("默认实现...");
    }
}
  • 静态方法和类中的静态方法没有区别,可以通过接口名直接调用
  • 静态方法不能被实现类重写
  • 默认方法实现类可以选择性重写
  • 如果一个类,父类中和接口中出现重名重参的方法,子类又没有重写的情况下,默认调用父类中的方法(类优先原则)
  • 如果一个类,实现的多个接口中出现**重名重参的方法,**子类又没有重写的情况下,直接编译时报错

1.3 Stream流

1.3.1 什么是Stream流

Stream流是一组用于数据集(集合/数组)操作的相关API。相对于原来的数据集操作,我们称之为外部迭代,也就是需要显示的写出循环遍历的过程。而Stream流提供了一种内部迭代的处理方式,内部迭代使得代码更加可读和优雅

1.3.2 Stream流的基本案例
public static void main(String[] args) {

    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    list.add(5);

    //外部迭代的方式
    for (Integer i : list) {
        System.out.println(i);
    }

    //通过Stream流内部迭代
    list.stream().forEach(new Consumer<Integer>() {
        @Override
        public void accept(Integer integer) {
            System.out.println(integer);
        }
    });
    
    //Stream流 + Lambda表达式
    list.stream().forEach(i -> System.out.println(i));
    
    //Stream流 + Lambda表达式 + 方法引用
    list.stream().forEach(System.out::println);
}
  • Stream流的操作步骤可以分为:创建流 -> 中间操作 -> 最终操作
1.3.3 创建流的方式
//1、通过函数创建
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);//枚举流
Stream<Integer> iterate = Stream.iterate(0, integer -> integer + 1);//无限流,通常需要配合limit操作使用
Stream<Double> generate = Stream.generate(Math::random);//无限流,通常需要配合limit操作使用

//2、集合转换成stream
//list转Stream
List<Integer> list = new ArrayList<>();
Stream<Integer> listStream = list.stream();

//set转stream
Set<String> set = new HashSet<>();
Stream<String> setStream = set.stream();

//map转stream
Map<Integer, String> map = new HashMap<>();
Stream<Map.Entry<Integer, String>> mapStream = map.entrySet().stream();

//3、数组转化成stream
int[] array = {1,2,3,4,5};
IntStream arrayStream = Arrays.stream(array);

//4、通过文件创建, 文件中每行数据就是一个流数据
try {
    Stream<String> fileStream = Files.lines(Paths.get("文件路径"), Charset.defaultCharset());
} catch (IOException e) {
    throw new RuntimeException(e);
}
1.3.4 中间操作

所有返回Stream对象的方法,都是中间操作。 Stream流的中间操作都是惰性操作,如果没有执行最终操作,则所有的中间操作都不会执行。

//filter - 过滤元素
Stream.of(1, 2, 3, 4, 5, 6)
        //留下所有偶数
        .filter(i -> i % 2 == 0)
        .forEach(System.out::println);
//map - 转换元素
List<Student> students = new ArrayList<>();
students.add(new Student().setId(1).setName("小明").setAge(19));
students.add(new Student().setId(2).setName("小红").setAge(22));
students.add(new Student().setId(3).setName("小刚").setAge(18));

//将集合转换成Stream流,得到一个存放学生对象的Stream
students.stream()
        //转换元素,将Stream流中的学生对象,转换成学生姓名,最终会获得一个存放学生姓名的Stream
        .map(stu -> stu.getName())
        .forEach(System.out::println);

//----学生类---
@Data
@Accessors(chain = true)
public static class Student {
    private Integer id;
    private String name;
    private Integer age;
}
//distinct - 去重
Stream.of(1,2,1,1,3,2,4,7,6,1).
        distinct()
        .forEach(System.out::println);
//sorted - 排序
Stream.of(5,18,26,14,17,22,1,3,19,3)
        //元素排序,注意排序的元素必须实现java.lang.Comparable接口
        .sorted()
        .forEach(System.out::println);
//limit - 限制
//创建一个无限流
Stream.generate(Math::random)
        //限制输出5个元素
        .limit(5)
        //将每个随机数乘以10000
        .map(d -> d * 10000)
        //将5个浮点随机数转换成整数型
        .map(Double::intValue)
        .forEach(System.out::println);
//skip - 跳过
Stream.of(1,2,3,4,5,6)
        //跳过前3个元素
        .skip(3)
        .forEach(System.out::println);
//peek - 临时查看
Stream.of(1,2,3,4,5)
        //限制输出4个元素
        .limit(4)
        //想查看下限制后的结果,但是又不想终止流操作,可以使用peek来进行
        .peek(System.out::println)
        //过滤掉所有的奇数
        .filter(i -> i % 2 == 0)
        .forEach(System.out::println);
//map - 操作
Integer[][] strArrays = {{1,2,3},{4,5,6}};
Arrays.stream(strArrays)
        //将元素(一维数组)转换成一个流,并且对一维数组中的每个元素+1
        .map(objects -> Stream.of(objects).map(o -> o + 1))
        //直接打印的是两个流对象
        .forEach(System.out::println);

//flatMap - 扁平化的转换
Integer[][] strArrays = {{1,2,3},{4,5,6}};
Arrays.stream(strArrays)
        //将元素(一维数组)转换成一个流,并且对一维数组中的每个元素+1
        .flatMap(objects -> Stream.of(objects).map(o -> o + 1))
        //直接会打印每个流中的元素
        .forEach(System.out::println);
1.3.5 最终操作

除了中间操作的其他方法,都是最终操作。 Stream流中只有调用了最终操作,整个流才会执行。

//forEach - 遍历流
Stream.of(1,2,3,4,5)
        .forEach(System.out::println);
//返回第一个元素
Integer i = Stream.of(1, 2, 3, 4, 5)
        .findFirst().get();
System.out.println("第一个元素:" + i);
//count - 统计数量
long count = Stream.of(1, 2, 3, 4, 5)
        .count();
System.out.println("流数据的数量为:" + count);
//sum求和
Integer sum = Stream.of(1,5,3,8)
        //将Stream流 转换为 IntStream
        .mapToInt(value -> value)
        //求和
        .sum();
System.out.println("求和的结果:" + sum);
//最小值
Integer min = Stream.of(1, 2, 3, 4, 5, 6)
        .min(Integer::compareTo)
        .get();
System.out.println("最小值:" + min);

//最大值
Integer max = Stream.of(1, 2, 3, 4, 5, 6)
        .max(Integer::compareTo)
        .get();
System.out.println("最大值:" + max);
//anyMatch - 任意元素符合条件
boolean flag = Stream.of(1, 2, 3, 4, 5, 6)
        .anyMatch(i -> i % 3 == 0);
System.out.println("是否存在能被3整除的元素?" + flag);
//allMatch - 所有元素符合条件
boolean flag = Stream.of(1, 2, 3, 4, 5, 6)
        .allMatch(i -> i % 3 == 0);
System.out.println("是否所有元素都能被3整除?" + flag);
//noneMatch - 所有元素都不符合条件
boolean flag = Stream.of(1, 2, 4, 5)
        .noneMatch(i -> i % 3 == 0);
System.out.println("是否没有找到能被3整除的元素?" + flag);
//1个参数的方式
Integer s = Stream.of(1, 2, 3, 4, 5, 6)
        //通过reduce进行求和
        //参数1 - 中间每个元素的结合过程
        .reduce((sum, i) -> sum += i).get();
System.out.println("求和的结果:" + s);

//2个参数的方式
Integer s2 = Stream.of(1, 2, 3, 4, 5, 6)
        //通过reduce进行求和
        //参数1 - 初始值
        //参数2 - 中间每个元素的结合过程
        .reduce(0, (sum, i) -> sum += i);
System.out.println("求和的结果:" + s2);

//3个参数的方式
Integer s3 = Stream.of(1,2,3,4,5,6)
        .parallel()
        //通过reduce进行求和
        //参数1 - 初始值
        //参数2 - 中间每个元素的结合过程
        //参数3 - 并行流的结合过程,需要配合.parallel()方式(将串型流变成并行流)一起使用
        .reduce(0, (sum, i) -> sum += i, (t1Val, t2Val) -> t1Val += t2Val);
System.out.println("求和的结果:" + s3);
//准备一个单词数组
String[] words = {"Jerry", "Jack", "Lili", "Tom", "Today", "Jack", "Tom", "Lili", "Tom"};
Map<String, Integer> reduce = Arrays.stream(words)
        .map(s -> {
            Map<String, Integer> map = new HashMap<>();
            map.put(s, 1);
            return map;
        }).reduce((stringIntegerMap, stringIntegerMap2) -> {
            for (Map.Entry<String, Integer> entry : stringIntegerMap2.entrySet()) {
                stringIntegerMap.computeIfPresent(entry.getKey(), (s, val) -> val + entry.getValue());
                stringIntegerMap.computeIfAbsent(entry.getKey(), s -> 1);
            }
            return stringIntegerMap;
        }).get();

System.out.println("统计的结果:" + reduce);
//将流转换成List集合
List<Integer> list = Stream.of(1, 2, 3, 4, 5, 6)
        .collect(Collectors.toList());

//将流转换成Map集合
Set<Integer> set = Stream.of(1, 2, 3, 4, 5, 6)
        .collect(Collectors.toSet());

//转换成Map集合
Map<String, String> map = Stream.of("key1-value1", "key2-value2", "key3-value3")
        .collect(Collectors.toMap(
                //参数1 - 根据流数据获得Map的key
                s -> s.split("-")[0],
                //参数2 - 根据流数据获得Map的Value
                s -> s.split("-")[1]
        ));
String[] words = {"Jerry", "Jack", "Lili", "Tom", "Today", "Jack", "Tom", "Lili", "Tom"};
Map<String, Integer> wordNum = Arrays.stream(words).collect(Collectors.toMap(
        //参数一 - 将单词本身作为key
        s -> s,
        //参数二 - 每个单词默认出现1次,作为value
        s -> 1,
        //参数三 - 如果遇到key相同时的解决方案,val1和val2分别表示key相同的value值,这里为了统计单词出现的次数,将value进行累加
        (val1, val2) -> val1 + val2
));
System.out.println("单词数量的统计:" + wordNum);

1.4 Optional类

Optional类主要是为了解决操作空对象而设计的一种类

1.4.1 创建Optional类
//1、创建一个空的Optional
Optional<Object> empty = Optional.empty();

//2、创建一个非空的Optional,如果传入的对象为空,则会报指针异常
Object obj = null;
Optional<Object> optional = Optional.of(obj);//如果obj为空,则这行会报空指针异常

//3、创建一个可为空的Optional,如果传入的对象为空,则什么也不会发生
Object obj2 = null;
Optional<Object> optional2 = Optional.ofNullable(obj2);
1.4.2 常用方法
Person person = Optional.of(person)
    //直接获得Optional中的对象,如果对象为空,则该方法报错
    .get();
String name = Optional.of(person)
    //将person对象转换成name字符串
    .map(Person::getName)
    //直接获得Optional中的对象,如果对象为空,则该方法报错
    .get();
//看个案例
//Person类中的name也是一个Optional类型
@Data
private static class Person{
    private Optional<String> name;
    private Integer age;
}

//使用map转换的结果是Optinal<Optional<String>>
Optional<Optional<String>> op1 = Optional.of(person).map(Person::getName);
//使用flatMap转换的结果是Optional<String>,直接消除了内部的Optional对象
Optional<String> op2 = Optional.of(person).flatMap(Person::getName);
Person person = null;
//创建一个允许为null的Optional对象
Optional.ofNullable(person)
        //如果对象不为空就执行以下代码,如果为空就不执行,也不会报错
        .ifPresent(p -> {
            System.out.println("处理了p对象....");
        });
Person person = new Person();
Optional.ofNullable(person)
        .ifPresentOrElse(person1 -> {
            //对象不为空时执行的代码
            System.out.println("person对象不为空!");
        }, () -> {
            //对象为空时执行的代码
            System.out.println("person对象为空!");
        });
Person person = null;
Person p = Optional.ofNullable(person)
        //如果person为空,会执行or方法,提供一个默认的Option<Person>对象
        .or(() -> Optional.of(new Person())).get();
Person person = null;
Person p = Optional.ofNullable(person)
       //如果person为空,则直接返回默认对象
       //和get方法不同的是,person为空,get方法会直接报错
       .orElse(new Person());
Person person = null;
Person p = Optional.ofNullable(person)
      //如果person为空,则创建一个默认对象再返回
      //和orElse的区别在于,orElse无论person是否为空,都会创建默认对象
      //而orElseGet只有确定了person为空,才会创建默认对象
      .orElseGet(Person::new);
Person person = null;
Person p = Optional.ofNullable(person)
       //和get一样,person为空时报错,但是该方法可以自定义抛出的异常
       .orElseThrow(NullPointerException::new);

1.5 新的日期和时间类

JDK8提供了一套新的日期时间处理类

1.5.1 新增的日期类
  • LocalDate - 表示日期
  • LocalTime - 表示时间
  • LocalDateTime - 表示日期时间
1.5.2 LocalDateTime的创建
//获得当前时间
LocalDateTime now = LocalDateTime.now();

//指定时间 1999-12-13 18:00:00
LocalDateTime ldate = LocalDateTime.of(1999, 12, 13, 18, 00, 00);
1.5.3 LocalDateTime的相关方法
//获得当前时间
LocalDateTime now = LocalDateTime.now();

//获得年份
int year = now.getYear();
//获得月份
int monthValue = now.getMonthValue();
//获得日期
int dayOfMonth = now.getDayOfMonth();
//获得这一年的第几天
int dayOfYear = now.getDayOfYear();
//获得星期几
int week = now.getDayOfWeek().getValue();
//获得小时
int hour = now.getHour();
//获得分钟
int minute = now.getMinute();
//获得秒
int second = now.getSecond();


//转成LocalDate对象
LocalDate localDate = now.toLocalDate();
//转换成LocalTime对象
LocalTime localTime = now.toLocalTime();

//增加时间 - 注意,以下方法都会返回新的日期对象,而不是修改原有的日期对象
//添加年份
now.plusYears(1);
//添加月份,会自定进位
now.plusMonths(1);
//添加日期,会自动进位
now.plusDays(1);
//添加小时,会自定进位
now.plusHours(1);
//添加分钟,会自定进位
now.plusMinutes(1);
//添加秒,会自定进位
now.plusSeconds(1);

//另外还有
//minusXxxx方法表示减少
//withXxxx方法表示设置
1.5.4 LocalDateTime格式化
//字符串转LocalDateTime对象
String time = "2023-01-05 15:33:26";
LocalDateTime date = LocalDateTime.parse(time, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(date);

//LocalDateTime对象 转 字符串
LocalDateTime now = LocalDateTime.now();
String nowStr = now.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒SSS毫秒"));
System.out.println(nowStr);
1.5.5 LocalDateTime和Date对象之间的互转
//LocalDateTime转Date
LocalDateTime localDateTime = LocalDateTime.now();
Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
System.out.println("date:" + date);

//Date转LocalDateTime
Date date2 = new Date();
LocalDateTime localDateTime2 = LocalDateTime.ofInstant(date2.toInstant(), ZoneId.systemDefault());
System.out.println(localDateTime2);

1.6 其他

  • JVM使用元空间代替了永久代,元空间使用的是直接内存空间
  • ConcurrentHashMap底层由分段锁优化成了CAS + synchronized
  • HashMap底层哈希碰撞的解决方案引入了红黑树
  • 引入了重复注解@Repeatable

二、JDK9的新特性(2017-9)

2.1 Reactive响应式编程

2.1.1 响应式流的概念

Reactive Stream (响应式流/反应流) 是JDK9引入的一套标准,是一套基于发布/订阅模式的数据处理规范。 它旨在解决处理元素流的问题——如何将元素流从发布者传递到订阅者,而不需要发布者阻塞,或订阅者有无限制的缓冲区或丢弃。 Reactive流目的是“找到最小的一组接口,方法和协议,用来描述必要的操作和实体以实现这样的目标:以非阻塞背压方式实现数据的异步流”。

2.1.2 订阅者和发布者之间的交互

2.1.3 JDK9 中的Reactive Stream规范的实现

Publisher:数据的发布者(生产者) Subscriber:数据订阅者(消费者) Subscription:发布者与订阅者之间的关系纽带,订阅令牌 Processor:数据处理器

Publisher发布者 - 接口

Publisher将数据流发布给注册的Subscriber。 通常使用Executor异步发布数据给订阅者。 Publisher需要确保每个订阅的Subscriber方法严格按顺序调用。 方法:subscribe - 使订阅者订阅发布者 public void subscribe(Subscriber<? super T> subscriber);

Subscriber订阅者 - 接口

Subscriber订阅Publisher的数据流,并接受回调。 如果Subscriber没有发出请求,就不会收到数据。对于给定订阅合同(Subscription),调用Subscriber的方法是严格按顺序的。 方法:onSubscribe - 发布者调用订阅者的这个方法异步传递订阅,这个方法在publisher.subscribe()方法调用后被执行。 public void onSubscribe(Flow.Subscription subscription); 方法:onNext - 发布者调用这个方法传递数据给订阅者。 public void onNext(Object item); 方法:onError - 当Publisher或者Subscriber遇到不可恢复的错误时调用此方法,之后不会再调用其他方法。 public void onError(Throwable throwable); 方法:onComplete - 当数据已经发送完成,并且没有错误导致订阅终止时,调用此方法,之后不在调用其他方法。 public void onComplete();

Subscription订阅合同 - 接口

Subscription用于链接Publisher和SubScriber。 Subscriber只有在请求时才会收到项目,并可以通过Subscription取消订阅。 方法:request - 订阅者调用此方法请求数据 public void request(long n); 方法:cancel - 订阅者调用这个方法来取消订阅,解除订阅者与发布者之间的关系。 public void cancel();

Processor处理器 - 接口

Processor位于Publisher和Subscriber之间,用于做数据转换。 可以有多个Processor同时使用,组成一个处理链,链中最后一个处理器的处理结果发送给Subscriber。 JDK没有提供任何具体的处理器。 处理器同时是订阅者和发布者,同时继承了两者。即作为订阅者接受数据,然后处理,处理后作为发布者再发布出去。 接口的声明: public static interface Processor<T,R> extends Subscriber, Publisher {}

2.1.4 JDK9中Reactive Stream规范调用流程

Publisher是能够发出元素的发布者,Subscriber是接收元素并做出响应的订阅者。当执行Publisher里的subscribe方法时,发布者会回调订阅者的onSubscribe方法,这个方法中,通常订阅者会借助传入的Subscription向发布者请求n个数据。然后发布者通过不断调用订阅者的onNext方法向订阅者发出最多n个数据。如果数据全部发完,则会调用onComplete告知订阅者流已经发完;如果有错误发生,则通过onError发出错误数据,同样也会终止流。

2.1.5 使用案例
public static void main(String[] args) {
        //1、创建生产者Publisher 实现了Publisher接口
        SubmissionPublisher<String> publisher = new SubmissionPublisher();
        //2、创建订阅者Subscriber,需要自己去实现内容方法
        Flow.Subscriber<String> subscriber = new Flow.Subscriber<String>() {
            private Flow.Subscription subscription;
            @Override
            public void onSubscribe(Flow.Subscription subscription) {
                this.subscription = subscription;
                System.out.println("订阅成功...");
                subscription.request(2);
                System.out.println("订阅方法请求了一个数据");
            }
            @Override
            public void onNext(String item) {
                System.out.println("订阅者接受到数据:" + item);
                if (item.equals("5")) throw new NullPointerException("xxxxx");
                subscription.request(3);
                System.out.println("订阅者继续请求数据");
            }
            @Override
            public void onError(Throwable throwable) {
                System.out.println("订阅出现异常:" + throwable.getMessage());
                subscription.cancel();
            }
            @Override
            public void onComplete() {
                System.out.println("数据已经接收完成!");
            }
        };
        //3、发布者与订阅者建立订阅关系,就是回调订阅者的onSubscribe方法
        publisher.subscribe(subscriber);
        //4、发布者生成数据
        for (int i = 0; i < 10; i++) {
            System.out.println("生成数据:" + i);
            publisher.submit(i + "");
        }
        //5、发布完成,关闭发送,此时会回调订阅者的onComplete方法
        publisher.close();
        //主线程等待
        while(true){}
    }

publisher.submit(i)方法是阻塞的,内部有个缓冲数组最大容量就是256,只有当订阅者发送 subscription.request(1); 请求后,才会从缓冲数组里拿按照顺序拿出数据传给 onNext方法 供订阅者处理,当subscription.request(1)这个方法被调用后,发布者发现数组里没有满才会再生产数据,这样就防止了生产者一次生成过多的数据把订阅者压垮,从而实现了背压机制

2.1.6 背压机制

如果生产者发出的信息比消费者能够处理消息最大量还要多,消费者可能会被迫一直在抓消息,耗费越来越多的资源,埋下潜在的崩溃风险。为了防止这一点,需要有一种机制使消费者可以通知生产者,降低消息的生成速度。生产者可以采用多种策略来实现这一要求,这种机制称为背压。 Subscription相当于是连接Publisher和Subscriber的“纽带(合同)”。因为当发布者调用subscribe方法注册订阅者时,会通过订阅者的回调方法onSubscribe传入Subscription对象,之后订阅者就可以使用这个Subscription对象的request方法向发布者“要”数据了。背压机制正是基于此来实现的。

2.2 其他

  • 私有接口方法,接口中可以使用private修饰方法
  • G1成为JVM默认的GC回收器
  • 支持http2.0和websocket的API
  • 优化了String的存储方式,底层由char数组变成byte数组
  • 模块化系统
  • 轻量级的JSON API

三、JDK10的新特性(2018-3)

3.1 支持局部变量类型推断

public class VariableDemo {

    //全局变量不能用var修饰,会报错
    var s = "";

    public static void main(String[] args) {
        //局部变量可以使用var声明,编译器可以自动类型推导
        var str = "";
        var i = 10;
        var s = new Student();
        
        //使用var时,必须声明时直接赋值,以下写法是错误的
        var x;
        x = 10;
        
        //var可以用于增强循环中临时变量的类型修饰,以下写法是正确的
        int[] array = {};
        for (var i1 : array) {
            
        }
    }

    private static class Student{

    }
}

JDK10之后,Java从强类型语句,变成了弱类型语言

3.2 Optional类的调整

使用**orElseThrow()方法来代替get()**方法,但是get()方法仍然可以使用

3.3 其他

  • G1底层优化成为并行的full GC
  • 修改了增强型for循环的字节码生成
  • 新增了集合的部分API

四、JDK11 的新特性(2018-9长支持版本)

4.1 对Stream API进行增强

//jdk8中lambda表达式的写法
o -> System.out.println(o);

//如果要给变量添加注解就需要写出变量的具体类型
(@NotNull String o) -> System.out.println(o);

//jdk11后,可以使用var来代替lambda表达式中的具体类型
(@NotNull var o) -> System.out.println(o);

4.2 对Collection API进行增强

//jdk11后,允许采用这种方法将List集合转换成相应类型的数组
String[] strings = list.toArray(String[]::new);
4.3 其他
  • 引入了ZGC垃圾回收器(预览版)
  • 支持Unicode 10
  • 该版本主要哦是对JDK9和JDK10的完善

五、JDK12的新特性(2019-3)

5.1 switch语法的拓展(预览版)

//jdk12中switch的写法
int i = 10;
switch (i) {
    case 1,2,5 -> System.out.println("A");
    case 3,4,6 -> System.out.println("B");
    case 7,8,9,10 -> {
        System.out.println("C");
        System.out.println("D");
    }
    default -> System.out.println("E");
}

//jdk12中,switch可以拥有返回值,但是如果返回结果,就不能进行复杂的业务处理
var s = "A";
//接收返回值
String result = switch (s) {
    case "A", "B" -> "优秀";
    case "C" -> "及格";
    default ->  "不及格";
};

5.2 其他

  • 新增Shenandoah GC算法
  • 优化了G1垃圾回收器

六、JDK13的新特性(2019-9)

6.1 switch语法的拓展

//jdk13对switch再次增强,可以在进行业务处理的同时,通过yield返回结果,类似方法的return
var s = "A";
//接收返回值
String result = switch (s) {
    case "A", "B" -> "优秀";
    case "C" -> "及格";
    default ->  {
        System.out.println("一些业务处理");
        yield "不及格";
    }
};

6.2 文本块(预览版)

//jdk13中 对一段文本可以使用文本块的形式表示
String text = """
        <html>
             <body>
                 <p>Hello, world</p>
             </body>
         </html>
        """;

6.3 其他

  • Socket底层实现优化,引入NIO
  • 优化了ZGC回收器

七、JDK14的新特性(2020-3)

7.1 instanceof语法增强(预览版)

//---jdk14之前的写法---
Object o = "Hello";
if (o instanceof String) {
    //需要强转
    String s = (String) o;
    //才能调用字符串中的方法
    System.out.println(s.toLowerCase());
}

//---jdk14之后的写法---
Object o = "Hello";
if (o instanceof String s) {
    //直接使用字符串中的方法
    System.out.println(s.toLowerCase());
} else {
    //这里不能使用s
}

7.2 引入Record类(预览版)

/**
 * Record类 - 记录类
 * 1、默认生成一个全参的构造方法
 * 2、给每个属性生成一个get方法,属性名就是方法名
 * 3、默认重写了equasl、hashcode、toString方法
 * 4、record类是final修饰的,所有属性也是final修饰的,所有没有生成set方法
 * 5、record类中,不能在声明普通属性,只能声明静态属性
 * 6、record类中,可以声明普通方法
 *
 * @param name
 * @param age
 */
public record Student(String name, int age) {

    public static String address;

    public void methtod(){

    }
}

//使用Record类
public static void main(String[] args) {
    Student student = new Student("张三", 18);
    System.out.println(student);
}

7.3 其他

  • NullPointerException输出优化

八、JDK15的新特性(2020-9)

8.1 其他

  • 文本块特性转为正式版
  • Record记录类(第二版预览)
  • CharSequence类增强(引入了isEmpty方法)
  • 引入了隐藏类 hidden class(预览版)
  • 引入了密封类 sealed class(预览版)
  • ZGC转为正式版

九、JDK16的新特性(2021-3)

9.1 其他

  • instanceof语法增强转为正式版
  • Record类转为正式版
  • 提供向量API(第一次孵化),提供了jdk.incubator.vector来用于矢量计算
  • JDK16相当于将JDK14、JDK15的一些特性进行了正式的引入

十、JDK17的新特性(2021-9长支持版本)

10.1 switch匹配模式支持instanceof匹配(预览版)

//jdk17之前的写法
Object o = null;
if (o instanceof String s) {
    //对s的处理
} else if (o instanceof Double d) {
    //对d的处理
} else if (o instanceof Integer i) {
    //对i的处理
}

//jdk17后,Switch可以和instanceof关键字结合使用
Object o = null;
switch (o){
    case String s -> s.toLowerCase();
    case Double d -> d.byteValue();
    case Integer i -> i.longValue();
    default -> throw new RuntimeException("类型未找到");
}

9.1 其他

  • 密封类 sealed class转为正式版
  • 向量API(第二次孵化)
  • 为伪随机数生成器提供新的接口类型和实现
  • 弃用Applet API
  • 强封装JDK的内部API

十一、JDK18的新特性(2022-03)

11.1 其他

  • 默认UTF-8字符编码
  • 提供一个简单的Web服务器
  • JavaDoc支持代码片段
  • 优化了反射核心功能
  • 向量API(第三次孵化)
  • 提供互联网地址解析的SPI
  • switch匹配模式(第二次预览)

十二、JDK19的新特性(2022-09)

12.1 虚拟线程(预览版)

12.2 其他

  • 向量API(第四次孵化)
  • switch匹配模式(第三次预览)
  • Record记录模式(预览版)

十三、JDK20的新特性(2023-03)

13.1 其他

  • 作用阈值(孵化阶段)
  • Record记录模式(第二次预览)
  • switch的匹配模式(第四次预览)
  • 虚拟线程(第二次预览)
  • 向量API(第五次孵化)