九天猎人 发表于 2024-8-28 04:16:35

第18章_JDK8-17新特性

此笔记中略的部分,在宋红康老师的视频中和其附带的笔记,都有具体内容,这里给出视频地点。
本章专题与脉络

https://img2024.cnblogs.com/blog/2883613/202408/2883613-20240828002442117-1440483461.jpg
1.Java版本迭代概述

1.1 发布特点(小步快跑,快速迭代)

发行版本发行时间备注Java 1.01996.01.23Sun 公司发布了 Java 的第一个开发工具包Java 5.02004.09.30①版本号从 1.4 直接更新至 5.0;②平台更名为Java SE、Java EE、Java MEJava 8.02014.03.18此版本是继 Java 5.0 以来变革最大的版本。是长期支持版本(Long-Term Support) 缩写LTSJava 9.02017.09.22此版本开始,每半年更新一次Java 10.02018.03.21Java 11.02018.09.25JDK 安装包取消独立 JRE 安装包,是长期支持版本(LTS)Java 12.02019.03.19.......Java17.02021.09发布 Java 17.0,版本号也称为 21.9,是长期支持版本(LTS)......Java21.02023.09.19发布 Java21.0,也是长期支持版本(LTS)这意味着 Java 的更新从传统的以特性驱动的发布周期,转变为以时间驱动的发布模式,并且承诺不会跳票。通过这样的方式,开发团队可以把一些关键特性尽早合并到 JDK 之中,以快速得到开发者反馈,在肯定程度上避免出现像Java 9 两次被迫延长发布的窘况。
针对企业客户的需求,Oracle 将以三年为周期发布长期支持版本(long term support)。
Oracle 的官方观点认为:与 Java 7->8->9 相比,Java 9->10->11 的升级和8->8u20->8u40 更相似。
新模式下的 Java 版本发布都会包含很多变更,包罗语言变更和 JVM变更,这两者都会对 IDE、字节码库和框架产生庞大影响。此外,不仅会新增其他API,还会有 API 被删除(这在 Java 8 之前没有发生过)。
现在看这种发布策略是非常成功的,解开了 Java/JVM 演进的很多枷锁,至关告急的是,OpenJDK 的权力中央,正在转移到开发社区和开发者手中。在新的模式中,既可以利用 LTS 满足企业长期可靠支持的需求,也可以满足各种开发者对于新特性迭代的诉求。因为用 2-3 年的最小间隔粒度来试验一个特性,根本是不现实的。
1.2 名词解释

名词解释:Oracle JDK 和 Open JDK
这两个 JDK 最大差别就是许可证不一样。但是对于个人用户来讲,没区别。
Oracle JDKOpen JDK泉源Oracle 团队维护Oracle 和 Open Java 社区授权协议Java 17 及更高版本 Oracle Java SE 许可证
Java16 及更低版本甲骨文免费条款和条件(NFTC) 许可协议GPL v2 许可证关系由 Open JDK 构建,增加了少许内容是否收费2021 年 9 月起 Java17 及更高版本所有用户免费。16 及更低版本,个人用户、开发用户免费。2017 年 9 月起,所有版本免费对语法的支持一致一致名词解释:JEP
JEP(JDK Enhancement Proposals):jdk 改进提案,每当需要有新的设想时候,JEP 可以提出非正式的规范(specification),被正式认可的 JEP 正式写进 JDK 的发展路线图并分配版本号。
名词解释:LTS
LTS(Long-term Support)即长期支持。Oracle 官网提供了对 Oracle JDK 个别版本的长期支持,即使发发行了新版本,比如现在最新的 JDK19,在结束日期前,LTS 版本都会被长期支持。(出了 bug,会被修复,非 LTS 则不会再有补丁发布)所以,肯定要选一个 LTS 版本,不然出了毛病没人修复了。
1.3 各版本支持时间路线图

https://img2024.cnblogs.com/blog/2883613/202408/2883613-20240828002523818-1101397995.jpg
1.4 各版本先容
https://img2024.cnblogs.com/blog/2883613/202408/2883613-20240828002529678-2030610975.jpg
下面关于各个版本的新特性,了解一下即可。
jdk 9

特性太多,查看链接:
https://openjdk.java.net/projects/jdk9/
jdk 10

https://openjdk.java.net/projects/jdk/10/
286: Local-Variable Type Inference 局部变量类型推断
296: Consolidate the JDK Forest into a Single Repository JDK 库的合并
304: Garbage-Collector Interface 统一的垃圾回收接口
307: Parallel Full GC for G1 为 G1 提供并行的 Full GC
310: Application Class-Data Sharing应用步伐类数据(AppCDS)共享
312: Thread-Local Handshakes ThreadLocal 握手交互
313: Remove the Native-Header Generation Tool (javah) 移除 JDK 中附带的 javah 工具
314: Additional Unicode Language-Tag Extensions 使用附加的 Unicode 语言标记扩展
316: Heap Allocation on Alternative Memory Devices 能将堆内存占用分配给用户指定的备用内存装备
317: Experimental Java-Based JIT Compiler 使用 Graal 基于 Java 的编译器
319: Root Certificates 根证书
322: Time-Based Release Versioning 基于时间定于的发布版本
jdk 11

https://openjdk.java.net/projects/jdk/11/
181: Nest-Based Access Control 基于嵌套的访问控制
309: Dynamic Class-File Constants 动态类文件常量
315: Improve Aarch64 Intrinsics改进 Aarch64 Intrinsics 318: Epsilon: A No-Op Garbage Collector Epsilon — 一个 No-Op(无操作)的垃圾收集器
320: Remove the Java EE and CORBA Modules 删除 Java EE 和 CORBA 模块
321: HTTP Client (Standard) HTTPClient API
323: Local-Variable Syntax for Lambda Parameters 用于 Lambda 参数的局部变量语法
324: Key Agreement with Curve25519 and Curve448 Curve25519 和 Curve448 算法的密钥协议
327: Unicode 10
328: Flight Recorder 飞行记录仪
329: ChaCha20 and Poly1305 Cryptographic Algorithms ChaCha20 和 Poly1305 加密算法
330: Launch Single-File Source-Code Programs启动单一文件的源代码步伐
331: Low-Overhead Heap Profiling 低开销的 Heap Profiling
332: Transport Layer Security (TLS) 1.3 支持 TLS 1.3
333: ZGC: A Scalable Low-Latency Garbage Collector (Experimental) 可伸缩低延长垃圾收集器
335: Deprecate the Nashorn JavaScript Engine 弃用 Nashorn JavaScript 引擎
336: Deprecate the Pack200 Tools and API 弃用 Pack200 工具和 API
jdk 12

https://openjdk.java.net/projects/jdk/12/
189:Shenandoah: A Low-Pause-Time Garbage Collector (Experimental) 低暂停时间的 GC
230: Microbenchmark Suite 微基准测试套件
325: Switch Expressions (Preview) switch 表达式
334: JVM Constants API JVM 常量 API 340: One AArch64 Port, Not Two 只保存一个 AArch64 实现
341: Default CDS Archives 默认类数据共享归档文件
344: Abortable Mixed Collections for G1 可中断的 G1 Mixed GC
346: Promptly Return Unused Committed Memory from G1 G1 及时返回未使用的已分配内存
jdk 13

https://openjdk.java.net/projects/jdk/13/
350: Dynamic CDS Archives 动态 CDS 档案
351: ZGC: Uncommit Unused Memory ZGC:取消使用未使用的内存
353: Reimplement the Legacy Socket API 重新实现旧版套接字 API
354: Switch Expressions (Preview) switch 表达式(预览)
355: Text Blocks (Preview) 文本块(预览)
jdk 14

https://openjdk.java.net/projects/jdk/14/
305: Pattern Matching for instanceof (Preview) instanceof 的模式匹配
343: Packaging Tool (Incubator) 打包工具
345: NUMA-Aware Memory Allocation for G1 G1 的 NUMA-Aware 内存分配
349: JFR Event Streaming JFR 事件流
352: Non-Volatile Mapped Byte Buffers非易失性映射字节缓冲区
358: Helpful NullPointerExceptions 实用的NullPointerExceptions
359: Records (Preview) (预览)
361: Switch Expressions (Standard) Switch 表达式
362: Deprecate the Solaris and SPARC Ports弃用 Solaris 和 SPARC 端口
363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector 删除并发标记扫描(CMS)垃圾回收器
364: ZGC on macOS
365: ZGC on Windows
366: Deprecate the ParallelScavenge + SerialOld GC Combination 弃用 ParallelScavenge + SerialOld GC 组合
367: Remove the Pack200 Tools and API 删除Pack200 工具和 API
368: Text Blocks (Second Preview) 文本块 (再次预览)
370: Foreign-Memory Access API (Incubator) 外部存储器访问 API
jdk 15

https://openjdk.java.net/projects/jdk/15/
339: Edwards-Curve Digital Signature Algorithm (EdDSA) EdDSA 数字签名算法
360: Sealed Classes (Preview) 密封类(预览)
371: Hidden Classes 隐藏类
372: Remove the Nashorn JavaScript Engine 移除Nashorn JavaScript 引擎
373: Reimplement the Legacy DatagramSocket API 重新实现 Legacy DatagramSocket API
374: Disable and Deprecate Biased Locking 禁用方向锁定
375: Pattern Matching for instanceof (Second Preview) instanceof 模式匹配(第二
次预览)
377: ZGC: A Scalable Low-Latency Garbage Collector ZGC:一个可扩展的低延长垃圾收集器
378: Text Blocks 文本块
379: Shenandoah: A Low-Pause-Time Garbage Collector Shenandoah:低暂停时间垃圾收集器
381: Remove the Solaris and SPARC Ports 移除Solaris 和 SPARC 端口
383: Foreign-Memory Access API (Second Incubator) 外部存储器访问 API(第二次孵化版)
384: Records (Second Preview) Records(第二次预览)
385: Deprecate RMI Activation for Removal 废弃 RMI 激活机制
jdk 16

https://openjdk.java.net/projects/jdk/16/
338: Vector API (Incubator) Vector API(孵化器)
347: Enable C++14 Language Features JDK C++的源码中允许使用 C++14 的语言特性
357: Migrate from Mercurial to Git OpenJDK 源码的版本控制从Mercurial (hg) 迁移到 git
369: Migrate to GitHub OpenJDK 源码的版本控制迁移到 github 上
376: ZGC: Concurrent Thread-Stack ProcessingZGC:并发线程处理
380: Unix-Domain Socket Channels Unix 域套接字通道
386: Alpine Linux Port 将 glibc 的 jdk 移植到使用 musl 的alpine linux 上
387: Elastic Metaspace 弹性元空间
388: Windows/AArch64 Port 移植 JDK 到 Windows/AArch64
389: Foreign Linker API (Incubator) 提供 jdk.incubator.foreign 来简化 native code 的调用
390: Warnings for Value-Based Classes 提供基于值的类的警告
392: Packaging Tool jpackage 打包工具转正
393: Foreign-Memory Access API (Third Incubator)
394: Pattern Matching for instanceofInstanceof 的模式匹配转正
395: Records Records 转正
396: Strongly Encapsulate JDK Internals by Default 默认情况下,封装了 JDK 内部构件
397: Sealed Classes (Second Preview) 密封类
jdk 17

https://openjdk.java.net/projects/jdk/17/
306: Restore Always-Strict Floating-Point Semantics 恢复始终严酷的浮点语义
356: Enhanced Pseudo-Random Number Generators 增强型伪随机数生成器
382: New macOS Rendering Pipeline 新的 macOS 渲染管道
391: macOS/AArch64 Port macOS/AArch64 端口398: Deprecate the Applet API for Removal 弃用 Applet API 后续将进行删除
403: Strongly Encapsulate JDK Internals 强封装 JDK 的内部 API
406: Pattern Matching for switch (Preview) switch 模式匹配(预览)
407: Remove RMI Activation 删除 RMI 激活机制
409: Sealed Classes 密封类转正
410: Remove the Experimental AOT and JIT Compiler 删除实验性的AOT 和 JIT 编译器
411: Deprecate the Security Manager for Removal 弃用即将删除的安全管理器
412: Foreign Function & Memory API (Incubator) 外部函数和内存 API(孵化特性)
414: Vector API (Second Incubator) Vector API(第二次孵化特性)
415: Context-Specific Deserialization Filters 上下文特定的反序列化过滤器
1.5 JDK各版本下载毗连

Oracle JDK

https://www.oracle.com/java/technologies/downloads/archive/
个人用户免费,商业收费。
Open JDK

https://jdk.java.net/archive/
都免费。发起使用该版本,避免版权问题。
1.6 如何学习新特性

对于新特性,我们应该从哪几个角度学习新特性呢?

[*]语法层面:

[*]比如 JDK5 中的自动拆箱、自动装箱、enum、泛型
[*]比如 JDK8 中的 lambda 表达式、接口中的默认方法、静态方法
[*]比如 JDK10 中局部变量的类型推断
[*]比如 JDK12 中的 switch
[*]比如 JDK13 中的文本块

[*]API 层面:

[*]比如 JDK8 中的 Stream、Optional、新的日期时间、HashMap 的底层结构变革
[*]比如 JDK9 中 String 的底层结构
[*]新的 / 过时的 API

[*]底层优化

[*]比如 JDK8 中永久代被元空间替代、新的 JS 执行引擎
[*]比如新的垃圾回收器、GC 参数、JVM 的优化

2.Java8新特性:Lambda表达式

着实Java 8从2014年到现在,从发布的时间上,已经不算"新了"。只是为了表达是java8之后出现的,所以叫Java8新特性。一般语法上的新特性,是必须把握的,不仅可以或许进步开发效率,以及在各种框架中会使用到这些发布时间久的"新特性"了。
2.1 关于Java8新特性简介

Java 8 (又称为 JDK 8 或 JDK1.8) 是 Java 语言开发的一个告急版本。 Java 8 是 Oracle 公司于 2014 年 3 月发布,可以看成是自 Java 5 以来最具革命性的版本。Java 8 为 Java 语言、编译器、类库、开发工具与 JVM 带来了大量新特性。
https://img2024.cnblogs.com/blog/2883613/202408/2883613-20240828002543888-1611854296.jpg

[*]速度更快
[*]代码更少(增加了新的语法:Lambda 表达式)
[*]强大的 Stream API
[*]便于并行

[*]并行流就是把一个内容分成多个数据块,并用差别的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上进步步伐的执行效率。
[*]Java 8 中将并行进行了优化,我们可以很轻易的对数据进行并行操作。Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。

[*]最大化减少空指针异常:Optional
[*]Nashorn 引擎,允许在 JVM 上运行 JS 应用

[*]发音“nass-horn”,是德国二战时一个坦克的命名
[*]JavaScript 运行在 jvm 已经不是新鲜事了,Rhino 早在 jdk6 的时候已经存在。现在替代 Rhino,官方的解释是 Rhino 相比其他 JavaScript 引擎(比如Google 的 V8)着实太慢了,改造 Rhino 还不如重写。所以 Nashorn 的性能也是其一个亮点。
[*]Nashorn 项目在 JDK 9 中得到改进;在 JDK11 中 Deprecated,后续JDK15 版本中 remove。在 JDK11 中取以代之的是 GraalVM。(GraalVM 是一个运行时平台,它支持 Java 和其他基于 Java 字节码的语言,但也支持其他语言,如 JavaScript,Ruby,Python 或 LLVM。性能是 Nashorn 的 2 倍以上。)

2.2 冗余的匿名内部类

java.lang.Runnable 接口来定义任务内容,并使用 java.lang.Thread类来启动该线程。代码如下:
public static void main(String[] args) {
   new Thread(new Runnable() {
         @Override
         public void run() {
         System.out.println("多线程任务执行!");
         }
   }).start(); // 启动线程
}本着“一切皆对象”的头脑,这种做法是无可厚非的:首先创建一个 Runnable 接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动。
代码分析:
对于 Runnable 的匿名内部类用法,可以分析出几点内容:

[*]Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心;
[*]为了指定 run 的方法体,不得不需要 Runnable 接口的实现类;
[*]为了省去定义一个 RunnableImpl 实现类的麻烦,不得不使用匿名内部类;
[*]必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
[*]而实际上,似乎只有方法体才是关键所在。
2.3 Lambda及其使用举例

Lambda 是一个匿名函数,我们可以把 Lambda 表达式明白为是一段可以转达的代码(将代码像数据一样进行转达)。使用它可以写出更轻便、更灵活的代码。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到了提拔。

[*]从匿名内部类到Lambda的转换举例1
//未使用Lambda表达式
Runnable r1 = new Runnable() {
    @Override
    public void run() {
      System.out.println("wind");
    }
};
r1.run();

System.out.println("*********");

/** Lambda表达式写法*/
Runnable r2 = () -> {
    System.out.println("breeze");
};

r2.run();

[*]从匿名内部类到Lambda的转换举例2
//未使用Lambda表达式
Consumer<String> consumer = new Consumer<>(){
    @Override
    public void accept(String s) {
      System.out.println(s);
    }
};
consumer.accept("微风和睦");
//使用Lambda表达式
Consumer<String> consumer2 = (s) -> {System.out.println(s);};
consumer2.accept("风和日丽");2.4 语法

Lambda 表达式:在 Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:

[*]左侧:指定了 Lambda 表达式需要的参数列表
[*]右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即 Lambda 表达式要执行的功能
语法格式一:无参,无返回值
/**
* 语法格式1:无参无返回值
*/
@Test
public void demo1(){
    //未使用Lambda表达式
    Runnable r1 = new Runnable() {
      @Override
      public void run() {
            System.out.println("wind");
      }
    };
    r1.run();

    System.out.println("*********");

    /** Lambda表达式写法*/
    Runnable r2 = () -> {
      System.out.println("breeze");
    };

    r2.run();
}语法格式二:Lambda 需要一个参数,但是没有返回值。
/**
* 语法格式2:需要一个参数,但是无返回值
*/
@Test
public void demo2(){
    //未使用Lambda表达式
    Consumer<String> consumer = new Consumer<>(){
      @Override
      public void accept(String s) {
            System.out.println(s);
      }
    };
    consumer.accept("微风和睦");
    //使用Lambda表达式
    Consumer<String> consumer2 = (String s) -> {System.out.println(s);};
    consumer2.accept("风和日丽");
}语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
/**
* 语法格式3:数据类型可以省略,因为可以由编译器推断得出,称为类型推断
*/
@Test
public void demo3(){
    //语法格式3使用前
    Consumer<String> consumer1 = (String s) -> {System.out.println(s);};
    consumer1.accept("风和日丽");

    //语法格式3使用后
    Consumer<String> consumer2 = (s) -> {System.out.println(s);};
    consumer2.accept("天旋地转");

}语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略
/**
* 语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略
*/
@Test
public void demo4(){
    //语法格式4使用前
    Consumer<String> consumer1 = (s) -> {System.out.println(s);};
    consumer1.accept("天旋地转");

    //语法格式4使用后
    Consumer<String> consumer2 = s -> {System.out.println(s);};
    consumer2.accept("英明神武");
}语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
/**
* 语法格式5:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
*/
@Test
public void demo5(){
    //语法格式5使用前
    Comparator<Integer> com1 = new Comparator<Integer>() {
      @Override
      public int compare(Integer o1, Integer o2) {
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
      }
    };
    System.out.println(com1.compare(12,21));
    //语法格式5使用后
    Comparator<Integer> com2 = (o1,o2) -> {
      System.out.println(o1);
      System.out.println(o2);
      return o1.compareTo(o2);
    };
    System.out.println(com1.compare(22,21));
}语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
/**
* 语法格式6:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
*/
@Test
public void demo6(){
    //语法格式6使用前
    Comparator<Integer> com1 = (o1,o2) -> {
      return o1.compareTo(o2);
    };
    System.out.println(com1.compare(12,6));
    //语法格式六使用后
    Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);
    System.out.println(com2.compare(12,21));
}2.5 关于类型推断

在语法格式三 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,步伐依然可以编译,这是因为 javac 根据步伐的上下文,在编译的背景推断出了参数的类型。Lambda 表达式的类型依靠于上下文情况,是由编译器推断出来的。这就是所谓的“类型推断”。
https://img2024.cnblogs.com/blog/2883613/202408/2883613-20240828002609718-1604178829.jpg
/**
* 关于类型推断
* Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程
* 序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下
* 文环境,是由编译器推断出来的。这就是所谓的“类型推断”。
*/
@Test
public void demo8(){
    //类型推断 1
    ArrayList<String> list = new ArrayList<>();
    //类型推断 2
    int[] arr = {1, 2, 3};
}3.Java8新特性:函数式(Functional)接口

3.1 什么是函数式接口


[*]只包含一个抽象方法(Single Abstract Method,简称 SAM)的接口,称为函数式接口。当然该接口可以包含其他非抽象方法。
[*]你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
[*]我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,阐明这个接口是一个函数式接口。
[*]在 java.util.function 包下定义了 Java 8 的丰富的函数式接口
3.2 如何明白函数时接口

https://img2024.cnblogs.com/blog/2883613/202408/2883613-20240828002632834-2116311695.jpg

[*]Java 从诞生日起就是一直倡导“一切皆对象”,在 Java 里面面向对象(OOP)编程是一切。但是随着 python、scala 等语言的鼓起和新技术的挑战,Java 不得不做出调整以便支持更加广泛的技术要求,即 Java 不但可以支持 OOP 还可以支持 OOF(面向函数编程)

[*]Java8 引入了 Lambda 表达式之后,Java 也开始支持函数式编程。
[*]Lambda 表达式不是 Java 最早使用的。现在 C++,C#,Python,Scala 等均支持 Lambda 表达式。

[*]面向对象的头脑:

[*]做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情。

[*]函数式编程头脑:

[*]只要能获取到效果,谁去做的,怎么做的都不告急,重视的是效果,不重视过程。

[*]在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda 表达式的类型是函数。但是在 Java8 中,有所差别。在 Java8 中,Lambda 表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。
[*]简单的说,在 Java8 中,Lambda 表达式就是一个函数式接口的实例。这就是 Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用 Lambda 表达式来表示。
3.3 举例

/** * Represents an operation that accepts a single input argument and returns no * result. Unlike most other functional interfaces, {@code Consumer} is expected * to operate via side-effects. * *
This is a functional interface * whose functional method is {@link #accept(Object)}. * * @paramthe type of the input to the operation * * @since 1.8 */@FunctionalInterfacepublic interface Consumer {    /**   * Performs this operation on the given argument.   *   * @param t the input argument   */    void accept(T t);    /**   * Returns a composed {@code Consumer} that performs, in sequence, this   * operation followed by the {@code after} operation. If performing either   * operation throws an exception, it is relayed to the caller of the   * composed operation.If performing this operation throws an exception,   * the {@code after} operation will not be performed.   *   * @param after the operation to perform after this operation   * @return a composed {@code Consumer} that performs in sequence this   * operation followed by the {@code after} operation   * @throws NullPointerException if {@code after} is null   */    default Consumer andThen(Consumer
页: [1]
查看完整版本: 第18章_JDK8-17新特性