第18章_JDK8-17新特性

此笔记中略的部分,在宋红康老师的视频中和其附带的笔记,都有详细内容,这里给出视频地址。

本章专题与脉络

1.Java版本迭代概述

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

发行版本 发行时间 备注
Java 1.0 1996.01.23 Sun 公司发布了 Java 的第一个开发工具包
Java 5.0 2004.09.30 ①版本号从 1.4 直接更新至 5.0;②平台更名为Java SE、Java EE、Java ME
Java 8.0 2014.03.18 此版本是继 Java 5.0 以来变化最大的版本。是长期支持版本(Long-Term Support) 缩写LTS
Java 9.0 2017.09.22 此版本开始,每半年更新一次
Java 10.0 2018.03.21
Java 11.0 2018.09.25 JDK 安装包取消独立 JRE 安装包,是长期支持版本(LTS)
Java 12.0 2019.03.19
.... ...
Java17.0 2021.09 发布 Java 17.0,版本号也称为 21.9,是长期支持版本(LTS)
... ...
Java21.0 2023.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 JDK Open 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 各版本支持时间路线图


1.4 各版本介绍

下面关于各个版本的新特性,了解一下即可。

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 带来了大量新特性。

  • 速度更快

  • 代码更少(增加了新的语法: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 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。

 /**
 * 关于类型推断
 * 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 如何理解函数时接口

  • 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.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #accept(Object)}.
 *
 * @param <T> the type of the input to the operation
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Consumer<T> {

    /**
     * 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<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

作为参数,传递lambda表达式

//未使用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("风和日丽");

作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收 Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。

3.4 Java内置函数时接口

3.4.1 常用的

之前学过的接口,有些就是函数式接口,比如:

  • java.lang.Runnable
    • public void run()
  • java.lang.Iterable
    • public Iterator iterate()
  • java.lang.Comparable
    • public int compareTo(T t)
  • java.util.Comparator
    • public int compare(T t1, T t2)

3.4.2 四大核心函数式接口

函数式接口 称谓 泛型 用途
Consumer 消费型接口 T 对类型为 T 的对象应用操作,包含方法:void accept(T t)
Supplier 供给型接口 返回类型为 T 的对象,包含方法:T get()
Function<T, R> 函数型接口 T, R 对类型为 T 的对象应用操作,并返回结果。结果是 R 类型的对象。包含方法:R apply(T t)
Predicate 判断型接口 T 确定类型为 T 的对象是否满足某约束,并返回 boolean 值。包含方法:boolean test(T t)

3.4.3 其他接口

类型 1:消费型接口

消费型接口的抽象方法特点:有形参,但是返回值类型是 void

接口名 抽象方法 描述
BiConsumer<T,U> void accept(T t, U u) 接收两个对象用于完成功能
DoubleConsumer void accept(double value) 接收一个 double 值
IntConsumer void accept(int value) 接收一个 int 值
LongConsumer void accept(long value) 接收一个 long 值
ObjDoubleConsumer void accept(T t, double value) 接收一个对象和一个double 值
ObjIntConsumer void accept(T t, int value) 接收一个对象和一个 int值
ObjLongConsumer void accept(T t, long value) 接收一个对象和一个 long值

类型 2:供给型接口

这类接口的抽象方法特点:无参,但是有返回值

接口名 抽象方法 描述
BooleanSupplier boolean getAsBoolean() 返回一个 boolean 值
DoubleSupplier double getAsDouble() 返回一个 double 值
IntSupplier int getAsInt() 返回一个 int 值
LongSupplier long getAsLong() 返回一个 long 值

类型 3:函数型接口

这类接口的抽象方法特点:既有参数又有返回值

接口名 抽象方法 描述
UnaryOperator T apply(T t) 接收一个 T 类型对象,返回一个 T 类型对象结果
DoubleFunction R apply(double value) 接收一个 double值,返回一个 R 类型对象
IntFunction R apply(int value) 接收一个 int 值,返回一个 R 类型对象
LongFunction R apply(long value) 接收一个 long值,返回一个 R 类型对象
ToDoubleFunction double applyAsDouble(T value) 接收一个 T 类型对象,返回一个double
... ... ...

该函数型接口,太多不再举例。具体见宋红康老师视频课程第18章

类型 4:判断型接口

这类接口的抽象方法特点:有参,但是返回值类型是 boolean 结果。

接口名 抽象方法 描述
BiPredicate<T,U> boolean test(T t, U u) 接收两个对象
DoublePredicate boolean test(double value) 接收一个 double 值
IntPredicate boolean test(int value) 接收一个 int 值
LongPredicate boolean test(long value) 接收一个 long 值

4.Java8新特性:方法引用和构造器引用

表达式是可以简化函数式接口的变量或形参赋值的语法。而方法引用和构造器引用是为了简化 Lambda 表达式的。

4.1 方法引用

当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用!

方法引用可以看做是 Lambda 表达式深层次的表达。换句话说,方法引用就是Lambda 表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是 Lambda 表达式的一个语法糖。

语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

4.1.1 方法引用格式

  • 格式:使用方法引用操作符 :: 将类(或对象) 与 方法名分隔开来。
    • 两个:中间不能有空格,而且必须英文状态下半角输入
  • 如下三种主要使用情况:
    • 情况 1:对象 :: 实例方法名
    • 情况 2: :: 静态方法名
    • 情况 3: :: 实例方法名

4.1.2 方法引用前提

要求 1:

Lambda 体只有一句语句,并且是通过调用一个对象的/类现有的方法来完成的

例如:System.out 对象,调用 println()方法来完成 Lambda 体

Math 类,调用 random()静态方法来完成 Lambda 体

要求 2:

针对情况 1:函数式接口中的抽象方法 a ,在被重写时使用了某一个对象的方法b。如果方法 a 的形参列表、返回值类型与方法 b 的形参列表、返回值类型都相同,则我们可以使用方法 b 实现对方法 a 的重写、替换。

针对情况 2:函数式接口中的抽象方法 a, 在被重写时使用了某一个类的静态方法 b。如果方法 a 的形参列表、返回值类型与方法 b 的形参列表、返回值类型都相同,则我们可以使用方法 b 实现对方法 a 的重写、替换。

针对情况 3:函数式接口中的抽象方法 a ,在被重写时使用了某一个对象的方法b。如果方法 a 的返回值类型与方法 b 的返回值类型相同,同时方法 a 的形参列表中有 n 个参数,方法 b 的形参列表有 n-1 个参数,且方法 a 的第 1 个参数作为方法 b 的调用者,且方法 a 的后 n-1 参数与方法 b 的 n-1 参数匹配(类型相同或满足多态场景也可以)

例如:s->System.out.println(t)

() -> Math.random() 都是无参

4.1.3 举例

//情况1:对象::实例方法
@Test
public void demo1() {

    //Consumer##void  accept(T t);
    //1.匿名内部类
    Consumer<String> consumer = new Consumer<>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    };
    consumer.accept("微风和睦");
    //2.Lambda表达式
    Consumer<String> consumer2 = s -> System.out.println(s);
    consumer2.accept("风和日丽");

    //3.方法引用
    Consumer<String> consumer3 = System.out::println;
    consumer3.accept("学而不思则罔");
}

/**
 * Supplier##String get()
 */
@Test
public void demo2() {
    Person person = new Person("小明", 20);
    //1.匿名内部类
    Supplier<String> supplier = new Supplier<String>() {
        @Override
        public String get() {
            return person.getName();
        }
    };
    System.out.println(supplier.get());

    //2.Lambda表达式
    Supplier<String> supplier1 = () -> person.getName();
    System.out.println(supplier1.get());

    //3.方法引用
    Supplier<String> supplier2 = person::getName;
    System.out.println(supplier2.get());
}

/**
 * 情况2 类::静态方法
 */
//Comparator##int compare(T o1, T o2);
@Test
public void demo3() {
    //1.匿名内部类
    Comparator<Integer> com1 = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return Integer.compare(o1, o2);
        }
    };
    System.out.println(com1.compare(22, 21));

    //2.lambda表达式
    Comparator<Integer> con2 = (o1, o2) -> Integer.compare(o1, o2);
    System.out.println(con2.compare(2, 3));

    //3.方法引用
    Comparator<Integer> con3 = Integer::compare;
    con3.compare(3, 5);
}

//Function##R apply(T t);
//Math中的Long round(Double d);
@Test
public void demo4() {
    //1.匿名内部类
    Function<Double, Long> fun1 = new Function<Double, Long>() {
        @Override
        public Long apply(Double aDouble) {
            return Math.round(aDouble);
        }
    };

    //2.lambda表达式
    Function<Double, Long> fun2 = aDouble -> Math.round(aDouble);

    //3.方法引用
    Function<Double, Long> fun3 = Math::round;
}

/**
 * 情况3:类::实例方法
 */

//Comparator##int compare(T o1, T o2);
//String##int t1.compareTo(t2)
@Test
public void demo5(){

    //0.匿名内部类
    Comparator<String> comparator0 = new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.compareTo(o2);
        }
    };

    //1.Lambda表达式
    Comparator<String> comparator = (s1,s2) -> s1.compareTo(s2);
    System.out.println(comparator.compare("aab", "abb"));

    Comparator<String> comparator1 = String::compareTo;
    System.out.println(comparator1.compare("abb","aab"));
}

//BiPredicate##boolean test(T t, U u);
@Test
public void demo6(){

    //0.匿名内部类
    BiPredicate<String,String> biPredicate0 = new BiPredicate<String, String>() {
        @Override
        public boolean test(String s1, String s2) {
            return s1.equals(s2);
        }
    };

    //1.lambda表达式
    BiPredicate<String,String> biPredicate1 = (s1,s2) -> s1.equals(s2);
    //2.方法引用
    BiPredicate<String,String> biPredicate2 = String::equals;
}

@Test
public void demo7(){
    Person person = new Person("风", 23);

    //1.匿名内部类
    Function<Person,String> fun1 = new Function<Person, String>() {
        @Override
        public String apply(Person person) {
            return person.getName();
        }
    };
    System.out.println(fun1.apply(person));

    //2.lambda表达式
    Function<Person,String> fun2 = (p1) -> p1.getName();

    System.out.println(fun2.apply(person));
    //3.方法引用
    Function<Person,String> fun3 = Person::getName;
    System.out.println(fun3.apply(person));
}
//上面demo中使用的Person类
public class Person {

    private String name;

    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

4.2 构造器引用

当 Lambda 表达式是创建一个对象,并且满足 Lambda 表达式形参,正好是给创建这个对象的构造器的实参列表,就可以使用构造器引用。

格式:类名::new

举例

 //构造器引用
//Supplier<T>##get()
@Test
public void demo1() {

    //0.匿名内部类
    Supplier<Person> sup1 = new Supplier<Person>() {
        @Override
        public Person get() {
            return new Person();
        }
    };
    System.out.println(sup1.get());

    //1.方法引用
    Supplier<Person> sup2 = Person::new;
    System.out.println(sup2.get());
}

// Function<T,R>##R apply(T t)
@Test
public void demo2() {
    //0.匿名内部类
    Function<Integer, Person> fun1 = new Function<Integer, Person>() {
        @Override
        public Person apply(Integer age) {
            return new Person(age);
        }
    };
    System.out.println(fun1.apply(20));

    //1.构造器引用
    Function<Integer, Person> fun2 = Person::new;//调用的是Person##Person(int age)
    System.out.println(fun2.apply(23));
}

//BiFunction<T, U, R>##R apply(T t, U u)
@Test
public void demo3() {
    //0.匿名内部类
    BiFunction<String, Integer, Person> func1 = new BiFunction<String, Integer, Person>() {
        @Override
        public Person apply(String s, Integer integer) {
            return new Person(s, integer);
        }
    };

    //1.构造器引用
    BiFunction<String, Integer, Person> func2 = Person::new;//调用Person##Person(String name, int age)
    System.out.println(func2.apply("breeze", 20));
}

4.3 数组构造引用

当 Lambda 表达式是创建一个数组对象,并且满足 Lambda 表达式形参,正好是给创建这个数组对象的长度,就可以数组构造引用。

格式:数组类型名::new

//数组引用
//Function中的R apply(T t)
@Test
public void demo4(){

    //0.匿名内部类
    Function<Integer,Person[]> func1 = new Function<Integer, Person[]>() {
        @Override
        public Person[] apply(Integer length) {
            return new Person[length];
        }
    };

    //2.方法引用
    Function<Integer,Person[]> func2 = Person[]::new;;
    System.out.println(func2.apply(10).length);
}

5. Java8 新特性:强大的 Stream API

5.1 说明

Java8 中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。

Stream API ( java.util.stream) 把真正的函数式编程风格引入到 Java 中。这是目前为止对 Java 类库最好的补充,因为 Stream API 可以极大提供 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码。

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

5.2 为什么要使用Stream API

实际开发中,项目中多数数据源都来自于 MySQL、Oracle 等。但现在数据源可以更多了,有 MongDB,Radis 等,而这些 NoSQL 的数据就需要 Java 层面去处理。

5.3 什么是Stream

Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,讲的是数据,而 Stream 是有关计算的,讲的是计算。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。

注意:

①Stream 自己不会存储元素。

②Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream。

③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。即一旦执行终止操作,就执行中间操作链,并产生结果。

④ Stream 一旦执行了终止操作,就不能再调用其它中间操作或终止操作了。

5.4 Stream的散布操作

1- 创建 Stream 一个数据源(如:集合、数组),获取一个流。

2- 中间操作 每次处理都会返回一个持有结果的新 Stream,即中间操作的方法返回值仍然是 Stream 类型的对象。因此中间操作可以是个操作链,可对数据源的数据进行 n 次处理,但是在终结操作前,并不会真正执行。

3- 终止操作(终端操作) 终止操作的方法返回值类型就不再是 Stream 了,因此一旦执行终止操作,就结束整个 Stream 操作了。一旦执行终止操作,就执行中间操作链,最终产生结果并结束 Stream。

5.4.1 创建 Stream 实例

方式一:通过集合

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

  • default Stream stream() : 返回一个顺序流
  • default Stream parallelStream() : 返回一个并行流
//方式1 通过集合
@Test
public void demo1(){
    List<Person> list = PersonData.getPersons();
    //顺序流
    //default Stream<E> stream()
    Stream<Person> stream = list.stream();
    //并行流
    //default Stream<E> parallelStream(
    Stream<Person> personStream = list.parallelStream();
}

方式二:通过数组

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • static Stream stream(T[] array): 返回一个流
  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)
//方式2 通过数组
@Test
public void demo2(){
    int[] arr = {1,2,3,4};
    IntStream stream = Arrays.stream(arr);

}

方式三:通过 Stream 的 of()

可以调用 Stream 类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。

  • public static Stream of(T... values) : 返回一个流
//方式3 通过 Stream#of
@Test
public void demo3(){
    Stream<String> stream = Stream.of("AA", "BB", "CC", "DD");
}

5.4.2 一系列中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为惰性求值

1-筛选与切片

方 法 描 述
filter(Predicate p) 接收 Lambda , 从流中排除某些元素
distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。
若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补

2-映射

此处的映射,是类似y和x的一元方程的映射关系

方 法 描 述
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream。
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

3-排序

方法 描述
sorted() 产生一个新流,其中按自然顺序排序。
sorted(Comparator com) 产生一个新流,其中按比较器顺序排序

5.4.3 终止操作

  • 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
  • 流进行了终止操作后,不能再次使用。
方 法 描 述
collect(Collector c) 将流转换为其他形式。接收一个 Collector 接口的实现,
用于给 Stream 中元素做汇总的方法

Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。

另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

方 法 返回类型 作用
toList Collector<T, ?, List> 把流中元素收集到 List
List<Employee> emps= list.stream().collect(Collectors.toList());
方 法 返回类型 作用
toSet Collector<T, ?, Set> 把流中元素收集到 Set
Set<Employee> emps= list.stream().collect(Collectors.toSet());
方 法 返回类型 作用
toCollection Collector<T, ?, C> 把流中元素收集到创建的集合
Collection<Employee> emps =list.stream().collect(Collectors.toCollection(ArrayList::new));
方 法 返回类型 作用
counting Collector<T, ?, Long> 计算流中元素的个数
long count = list.stream().collect(Collectors.counting());
方 法 返回类型 作用
summingInt Collector<T, ?, Integer> 对流中元素的整数属性求和
int total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
方 法 返回类型 作用
averagingInt Collector<T, ?, Double> 计算流中元素 Integer 属性的平均值
double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary));
方 法 返回类型 作用
summarizingInt Collector<T, ?, IntSummaryStatistics> 收集流中 Integer 属性的统计值。如:平均值
int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
方 法 返回类型 作用
joining Collector<CharSequence, ?, String> 连接流中每个字符串
String str= list.stream().map(Employee::getName).collect(Collectors.joining());

还有其他终止操作略。

5.5 Java9 新增 API

新增 1:Stream 实例化方法

ofNullable()的使用:

中 Stream 不能完全为 null,否则会报空指针异常。而 Java 9 中的ofNullable 方法允许我们创建一个单元素 Stream,可以包含一个非空元素,也可以创建一个空 Stream。

6. 新语法结构

新的语法结构,为我们勾勒出了 Java 语法进化的一个趋势,将开发者从复杂、繁琐的低层次抽象中逐渐解放出来,以更高层次、更优雅的抽象,既降低代码量,又避免意外编程错误的出现,进而提高代码质量和开发效率。

6.1 Java 的 REPL 工具: jShell 命令

JDK9 的新特性

Java 终于拥有了像 Python 和 Scala 之类语言的 REPL 工具(交互式编程环境,read - evaluate - print - loop):jShell。以交互式的方式对语句和表达式进行求值。即写即得、快速运行。

利用 jShell 在没有创建类的情况下,在命令行里直接声明变量,计算表达式,执行语句。无需跟人解释”public static void main(String[] args)”这句"废话"。

实际中,使用较少,略。

6.2 异常处理之 try-catch 资源关闭

在 JDK7 之前,我们这样处理资源的关闭:

@Test
public void test01() {
     FileWriter fw = null;
     BufferedWriter bw = null;
     try {
         fw = new FileWriter("d:/1.txt");
         bw = new BufferedWriter(fw);
         bw.write("hello");
     } catch (IOException e) {
     	e.printStackTrace();
     } finally {
         try {
             if (bw != null) {
                bw.close();
             }
         } catch (IOException e) {
         	e.printStackTrace();
         }
         try {
            if (fw != null) {
             	fw.close();
         	}
         } catch (IOException e) {
         	e.printStackTrace();
         }
     }
}

JDK7 的新特性

在 try 的后面可以增加一个(),在括号中可以声明流对象并初始化。try 中的代码执行完毕,会自动把流对象释放,就不用写 finally 了。

格式:

try(资源对象的声明和初始化){
 	业务逻辑代码,可能会产生异常
}catch(异常类型1 e){
 	处理异常代码
}catch(异常类型2 e){ 
    处理异常代码
}

说明:

1、在 try()中声明的资源,无论是否发生异常,无论是否处理异常,都会自动关闭资源对象,不用手动关闭了。

2、这些资源实现类必须实现 AutoCloseable 或 Closeable 接口,实现其中的close()方法。Closeable 是 AutoCloseable 的子接口。Java7 几乎把所有的“资源类”(包括文件 IO 的各种类、JDBC 编程的 Connection、Statement 等接口…)都进行了改写,改写后资源类都实现了 AutoCloseable 或 Closeable 接口,并实现了 close()方法。

3、写到 try()中的资源类的变量默认是 final 声明的,不能修改。

@Test
public void test02() {
     try (
         FileWriter fw = new FileWriter("d:/1.txt");
         BufferedWriter bw = new BufferedWriter(fw);
     ) {
     	bw.write("hello");
     } catch (IOException e) {
     	e.printStackTrace();
     }
}

JDK9 的新特性

try 的前面可以定义流对象,try 后面的()中可以直接引用流对象的名称。在 try代码执行完毕后,流对象也可以释放掉,也不用写 finally 了。

格式:

A a = new A();
B b = new B();
try(a;b){
	可能产生的异常代码
}catch(异常类名 变量名){
	异常处理的逻辑
}
@Test
public void test04() {
     InputStreamReader reader = new InputStreamReader(System.in);
     OutputStreamWriter writer = new OutputStreamWriter(System.out);
     try (reader; writer) {
         //reader 是 final 的,不可再被赋值
         // reader = null;
     } catch (IOException e) {
     	e.printStackTrace();
     }
}

6.3 局部变量类型推断(个人不推荐使用)

JDK 10 的新特性

局部变量的显示类型声明,常常被认为是不必须的,给一个好听的名字反而可以很清楚的表达出下面应该怎样继续。本新特性允许开发人员省略通常不必要的局部变量类型声明,以增强 Java 语言的体验性、可读性。只是一个语法糖,编译过程中会把使用的var替换成各自的类型。

//1.局部变量的实例化
var list = new ArrayList<String>();
var set = new LinkedHashSet<Integer>();
//2.增强 for 循环中的索引
for (var v : list) {
 System.out.println(v);
}
//3.传统 for 循环中
for (var i = 0; i < 100; i++) {
 System.out.println(i);
}
//4. 返回值类型含复杂泛型结构
var iterator = set.iterator();
//Iterator<Map.Entry<Integer, Student>> iterator = set.iterator();
  • 不适用场景
    • 声明一个成员变量
    • 声明一个数组变量,并为数组静态初始化(省略 new 的情况下)
    • 方法的返回值类型
    • 方法的参数类型
    • 没有初始化的方法内的局部变量声明
    • 作为 catch 块中异常类型
    • Lambda 表达式中函数式接口的类型
    • 方法引用中函数式接口的类型

注意:

  • var 不是一个关键字,而是一个类型名,将它作为变量的类型。不能使用 var 作为类名。
  • 这不是 JavaScript。var 并不会改变 Java 是一门静态类型语言的事实。编译器负责推断出类型,并把结果写入字节码文件,就好像是开发人员自己敲入类型一样。

6.4 instanceof 的模式匹配

JDK14 中预览特性:

instanceof 模式匹配通过提供更为简便的语法,来提高生产力。有了该功能,可以减少 Java 程序中显式强制转换的数量,实现更精确、简洁的类型安全的代码。

Java 14 之前旧写法:

if(obj instanceof String){
 String str = (String)obj; //需要强转
 .. str.contains(..)..
}else{
 ...
}

Java 14 之前新写法:

if(obj instanceof String str){
 .. str.contains(..)..
}else{
 ...
}

举例:略

JDK15 中第二次预览:

没有任何更改。

JDK16 中转正特性:

在 Java16 中转正。

6.5 switch 表达式

  • 传统 switch 声明语句的弊端:
    • 匹配是自上而下的,如果忘记写 break,后面的 case 语句不论匹配与否都会执行; --->case 穿透
    • 所有的 case 语句共用一个块范围,在不同的 case 语句定义的变量名不能重复;
    • 不能在一个 case 里写多个执行结果一致的条件;
    • 整个 switch 不能作为表达式返回值。
//常见错误实现
switch(month){
     case 3|4|5://3|4|5 用了位运算符,11 | 100 | 101 结果是 111 是 7
     System.out.println("春季");
     break;
     case 6|7|8://6|7|8 用了位运算符,110 | 111 | 1000 结果是 1111 是 15
     System.out.println("夏季");
     break;
     case 9|10|11://9|10|11 用了位运算符,1001 | 1010 | 1011 结果是 1011
    是 11
     System.out.println("秋季");
     break;
     case 12|1|2://12|1|2 用了位运算符,1100 | 1 | 10 结果是 1111,是 15
     System.out.println("冬季");
     break;
     default:
     System.out.println("输入有误");
}

JDK12 中预览特性:

  • Java 12 将会对 switch 声明语句进行扩展,使用 case L ->来替代以前的 break;省去了 break 语句,避免了因少写 break 而出错。
  • 同时将多个 case 合并到一行,显得简洁、清晰,也更加优雅的表达逻辑分支。
  • 为了保持兼容性,case 条件语句中依然可以使用字符:,但是同一个 switch 结构里不能混用-> 和:,否则编译错误。

Java 12 之前

public class SwitchTest {
	public static void main(String[] args) {
		int numberOfLetters;
		Fruit fruit = Fruit.APPLE;
		switch (fruit) {
             case PEAR:
                 numberOfLetters = 4;
                 break;
             case APPLE:
             case GRAPE:
             case MANGO:
                 numberOfLetters = 5;
                 break;
             case ORANGE:
             case PAPAYA:
                numberOfLetters = 6;
                break;
             default:
                throw new IllegalStateException("No Such Fruit:" + fruit);
        }
		System.out.println(numberOfLetters);
	}
}
enum Fruit {
 PEAR, APPLE, GRAPE, MANGO, ORANGE, PAPAYA;
}

switch 语句如果漏写了一个 break,那么逻辑往往就跑偏了,这种方式既繁琐,又容易出错。

Java12中

public class SwitchTest1 {
    public static void main(String[] args) {
        Fruit fruit = Fruit.GRAPE;
        switch(fruit){
            case PEAR -> System.out.println(4);
            case APPLE,MANGO,GRAPE -> System.out.println(5);
            case ORANGE,PAPAYA -> System.out.println(6);
            default -> throw new IllegalStateException("No Such Fruit:" + fruit);
        };
	}
}

更进一步

public class SwitchTest2 {
    public static void main(String[] args) {
        Fruit fruit = Fruit.GRAPE;
        int numberOfLetters = switch(fruit){
            case PEAR -> 4;
            case APPLE,MANGO,GRAPE -> 5;
            case ORANGE,PAPAYA -> 6;
            default -> throw new IllegalStateException("No Such Fruit:" + fruit);
        };
    	System.out.println(numberOfLetters);
    }
}

JDK13 中二次预览特性:

JDK13 中引入了 yield 语句,用于返回值。这意味着,switch 表达式(返回值)应该使用 yield,switch 语句(不返回值)应该使用 break。

yield 和 return 的区别在于:return 会直接跳出当前循环或者方法,而 yield 只会跳出当前 switch 块。

在以前:

@Test
public void testSwitch1(){
    String x = "3";
    int i;
    switch (x) {
        case "1":
        	i=1;
        	break;
        case "2":
            i=2;
            break;
        default:
            i = x.length();
            break;
    }
    System.out.println(i);
}

在 JDK13 中:

@Test
public void testSwitch2(){
    String x = "3";
    int i = switch (x) {
        case "1" -> 1;
        case "2" -> 2;
        default -> {yield 3;}
    };
    System.out.println(i);
}
//或者
@Test
public void testSwitch3() {
    String x = "3";
    int i = switch (x) {
        case "1":
        	yield 1;
        case "2":
        	yield 2;
        default:
        	yield 3;
    };
    System.out.println(i);
}

JDK14 中转正特性:

这是 JDK 12 和 JDK 13 中的预览特性,现在是正式特性了。

JDK17 的预览特性:switch 的模式匹配

旧写法:

static String formatter(Object o) {
    String formatted = "unknown";
    if (o instanceof Integer i) {
    	formatted = String.format("int %d", i);
    } else if (o instanceof Long l) {
    	formatted = String.format("long %d", l);
    } else if (o instanceof Double d) {
    	formatted = String.format("double %f", d);
    } else if (o instanceof String s) {
    	formatted = String.format("String %s", s);
    }
    return formatted;
}

模式匹配新写法:

static String formatterPatternSwitch(Object o) {
    return switch (o) {
        case Integer i -> String.format("int %d", i);
        case Long l -> String.format("long %d", l);
        case Double d -> String.format("double %f", d);
        case String s -> String.format("String %s", s);
        default -> o.toString();
    };
}

直接在 switch 上支持 Object 类型,这就等于同时支持多种类型,使用模式匹配得到具体类型,大大简化了语法量,这个功能很实用。

6.6 文本块

现实问题:

在 Java 中,通常需要使用 String 类型表达 HTML,XML,SQL 或 JSON 等格式的字符串,在进行字符串赋值时需要进行转义和连接操作,然后才能编译该代码,这种表达方式难以阅读并且难以维护。

JDK13 的新特性

使用"""作为文本块的开始符和结束符,在其中就可以放置多行的字符串,不需要进行任何转义。因此,文本块将提高 Java 程序的可读性和可写性。

基本使用:

"""
line1
line2
line3
"""

相当于:
"line1\nline2\nline3\n"

或者一个连接的字符串:

"line1\n" +
"line2\n" +
"line3\n"

如果字符串末尾不需要行终止符,则结束分隔符可以放在最后一行内容上。
例如:
"""
line1
line2
line3"""

相当于
"line1\nline2\nline3"

文本块可以表示空字符串,但不建议这样做,因为它需要两行源代码:
String empty = """
""";

举例 1:普通文本

//原有写法:
String text1 = "The Sound of silence\n" +

 "Hello darkness, my old friend\n" +

 "I've come to talk with you again\n" +

 "Because a vision softly creeping\n" +

 "Left its seeds while I was sleeping\n" +

 "And the vision that was planted in my brain\n" +

 "Still remains\n" +

 "Within the sound of silence";

System.out.println(text1)
    
//新特性
String text2 = """
         The Sound of silence
         Hello darkness, my old friend
         I've come to talk with you again
         Because a vision softly creeping
         Left its seeds while I was sleeping
         And the vision that was planted in my brain
         Still remains
         Within the sound of silence
 """;
System.out.println(text2);

举例 2:HTML 语句

<html>
 <body>
 <p>Hello, 尚硅谷</p>
 </body>
</html>
将其复制到 Java 的字符串中,会展示成以下内容:
"<html>\n" +
" <body>\n" +
" <p>Hello, 尚硅谷</p>\n" +
" </body>\n" +
"</html>\n";
即被自动进行了转义,这样的字符串看起来不是很直观,在 JDK 13 中:
"""
<html>
 <body>
 <p>Hello, world</p>
 </body>
</html>
""";

举例 3:SQL 语句

select employee_id,last_name,salary,department_id
from employees
where department_id in (40,50,60)
order by department_id asc
//原有方式:
String sql = "SELECT id,NAME,email\n" +
             "FROM customers\n" +
             "WHERE id > 4\n" +
             "ORDER BY email DESC";
//使用新特性:
String sql1 = """
             SELECT id,NAME,email
             FROM customers
             WHERE id > 4
             ORDER BY email DESC
             """;

举例 4:JSON 字符串

//原有方式:
String myJson = "{\n" +
                 " \"name\":\"Song Hongkang\",\n" +
                 " \"address\":\"www.atguigu.com\",\n" +
                 " \"email\":\"shkstart@126.com\"\n" +
                 "}";
System.out.println(myJson);
//使用新特性:
String myJson1 = """
                 {
                 "name":"Song Hongkang",
                 "address":"www.atguigu.com",
                 "email":"shkstart@126.com"
                 }""";
System.out.println(myJson1);

JDK14 中二次预览特性

JDK14 的版本主要增加了两个 escape sequences,分别是\ \s escape sequence

public class Feature05 {
 //jdk14 新特性
    @Test
    public void test5(){
        String sql1 = """
                    SELECT id,NAME,email
                    FROM customers
                    WHERE id > 4
                    ORDER BY email DESC
                    """;
        System.out.println(sql1);
        // \:取消换行操作
        // \s:表示一个空格
        String sql2 = """
                    SELECT id,NAME,email \
                    FROM customers\s\
                    WHERE id > 4 \
                    ORDER BY email DESC
                    """;
        System.out.println(sql2);
    }
}

JDK15 中功能转正

6.7 Record

背景

早在 2019 年 2 月份,Java 语言架构师 Brian Goetz,曾写文抱怨“Java 太啰嗦”或有太多的“繁文缛节”。他提到:开发人员想要创建纯数据载体类(plain data carriers)通常都必须编写大量低价值、重复的、容易出错的代码。如:构造函数、getter/setter、equals()、hashCode()以及 toString()等。

以至于很多人选择使用 IDE 的功能来自动生成这些代码。还有一些开发会选择使用一些第三方类库,如 Lombok 等来生成这些方法。

JDK14 中预览特性:神说要用 record,于是就有了。实现一个简单的数据载体类,为了避免编写:构造函数,访问器,equals(),hashCode () ,toString ()等,Java 14 推出 record。

record 是一种全新的类型,它本质上是一个 final 类,同时所有的属性都是final 修饰,它会自动编译出 public gethashcodeequalstoString、构造器等结构,减少了代码编写量。

具体来说:当你用 record 声明一个类时,该类将自动拥有以下功能:

  • 获取成员变量的简单方法,比如例题中的 name() 和 partner() 。注意区别于我们平常 getter()的写法。
  • 一个 equals 方法的实现,执行比较时会比较该类的所有成员属性。
  • 重写 hashCode() 方法。
  • 一个可以打印该类所有成员属性的 toString() 方法。
  • 只有一个构造方法。

此外:

  • 还可以在 record 声明的类中定义静态字段、静态方法、构造器或实例方法。
  • 不能在 record 声明的类中定义实例字段;类不能声明为 abstract;不能声明显式的父类等。
public record Dog(String name, Integer age) {
}
public class Java14Record {
    public static void main(String[] args) {
        Dog dog1 = new Dog("牧羊犬", 1);
        Dog dog2 = new Dog("田园犬", 2);
        Dog dog3 = new Dog("哈士奇", 3);
        System.out.println(dog1);
        System.out.println(dog2);
        System.out.println(dog3);
    }
}
public class Feature07 {
    @Test
    public void test1(){
        //测试构造器
        Person p1 = new Person("罗密欧",new Person("zhuliye",null));
        //测试 toString()
        System.out.println(p1);
        //测试 equals():
        Person p2 = new Person("罗密欧",new Person("zhuliye",null));
        System.out.println(p1.equals(p2));
        //测试 hashCode()和 equals()
        HashSet<Person> set = new HashSet<>();
        set.add(p1);
        set.add(p2);
        for (Person person : set) {
        System.out.println(person);
        }
        //测试 name()和 partner():类似于 getName()和 getPartner()
        System.out.println(p1.name());
 		System.out.println(p1.partner());
    }
    @Test
    public void test2(){
        Person p1 = new Person("zhuyingtai");
        System.out.println(p1.getNameInUpperCase());
        Person.nation = "CHN";
        System.out.println(Person.showNation());
    }
}

public record Person(String name,Person partner) {
    //还可以声明静态的属性、静态的方法、构造器、实例方法
    public static String nation;
    public static String showNation(){
    	return nation;
    }
    public Person(String name){
    	this(name,null);
    }
    public String getNameInUpperCase(){
    	return name.toUpperCase();
	}
 //不可以声明非静态的属性
// private int id;//报错
}
//不可以将 record 定义的类声明为 abstract 的
//abstract record Order(){
//
//}
//不可以给 record 定义的类声明显式的父类(非 Record 类)
//record Order() extends Thread{
//
//}

JDK15 中第二次预览特性

JDK16 中转正特性

最终到 JDK16 中转正。

记录不适合哪些场景

record 的设计目标是提供一种将数据建模为数据的好方法。它也不是JavaBeans 的直接替代品,因为 record 的方法不符合 JavaBeans 的 get 标准。另外 JavaBeans 通常是可变的,而记录是不可变的。尽管它们的用途有点像,但记录并不会以某种方式取代 JavaBean。

6.8 密封类

背景:

在 Java 中如果想让一个类不能被继承和修改,这时我们应该使用 final 关键字对类进行修饰。不过这种要么可以继承,要么不能继承的机制不够灵活,有些时候我们可能想让某个类可以被某些类型继承,但是又不能随意继承,是做不到的。Java 15 尝试解决这个问题,引入了 sealed 类,被 sealed 修饰的类可以指定子类。这样这个类就只能被指定的类继承。

JDK15 的预览特性:

通过密封的类和接口来限制超类的使用,密封的类和接口限制其它可能继承或实现它们的其它类或接口。

具体使用:

  • 使用修饰符 sealed,可以将一个类声明为密封类。密封的类使用保留关键字permits 列出可以直接扩展(即 extends)它的类。
  • sealed 修饰的类的机制具有传递性,它的子类必须使用指定的关键字进行修饰,且只能是 finalsealednon-sealed 三者之一。
public abstract sealed class Shape permits Circle, Rectangle, Square {...}

public final class Circle extends Shape {...} //final 表示 Circle 不能再被继承了

public sealed class Rectangle extends Shape permits TransparentRectangle, FilledRectangle {...}

public final class TransparentRectangle extends Rectangle {...}

public final class FilledRectangle extends Rectangle {...}

public non-sealed class Square extends Shape {...} //non-sealed 表示可以允许任何类继承

JDK16 二次预览特性

JDK17 中转正特性

7. API 的变化

7.1 Optional 类

JDK8 的新特性

到目前为止,臭名昭著的空指针异常是导致 Java 应用程序失败的最常见原因。以前,为了解决空指针异常,Google 在著名的 Guava 项目引入了 Optional类,通过检查空值的方式避免空指针异常。受到 Google 的启发,Optional 类已经成为 Java 8 类库的一部分。

Optional 类(java.util.Optional) 是一个容器类,它可以保存类型 T 的值,代表这个值存在。或者仅仅保存 null,表示这个值不存在。如果值存在,则isPresent()方法会返回 true,调用 get()方法会返回该对象。

Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。

创建 Optional 类对象的方法:

  • static Optional empty() :用来创建一个空的 Optional 实例
    • static Optional of(T value) :用来创建一个 Optional 实例,value 必须非空
    • static Optional ofNullable(T value) :用来创建一个Optional 实例,value 可能是空,也可能非空
  • 判断 Optional容器中是否包含对象:
    • boolean isPresent() : 判断 Optional 容器中的值是否存在
    • void ifPresent(Consumer<? super T> consumer) :判断 Optional 容器中的值是否存在,如果存在,就对它进行 Consumer 指定的操作,如果不存在就不做
  • 获取Optional 容器的对象:
  • T get(): 如果调用对象包含值,返回该值。否则抛异常。T get()与 of(T value)配合使用
  • T orElse(T other):orElse(T other) 与 ofNullable(T value)配合使用,如果Optional 容器中非空,就返回所包装值,如果为空,就用 orElse(T other)other 指定的默认值(备胎)代
  • T orElseGet(Supplier<? extends T> other) :如果 Optional 容器中非空,就返回所包装值,如果为空,就用 Supplier 接口的 Lambda 表达式提供的值代替
  • T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果 Optional 容器中非空,就返回所包装值,如果为空,就抛出你指定的异常类型代替原来的NoSuchElementException

举例:略

这是 JDK9-11 的新特性

新增方法 描述 新增的版本
boolean isEmpty() 判断 value 是否为空 JDK 11
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) value 非空,执行参数 1 功能;如果 value 为空,执行参数 2 功能 JDK 9
Optional or(Supplier<? extends Optional<? extends T>> supplier) value 非空,返回对应的Optional;value 为空,返回形参封装的 Optional JDK 9
Stream stream() value 非空,返回仅包含此 value的 Stream;否则,返回一个空的Stream JDK 9
T orElseThrow() value 非空,返回 value;否则抛异常 NoSuchElementException JDK 10

7.2 String 存储结构和 API 变更

这是 JDK9 的新特性。

产生背景:

Motivation The current implementation of the String class stores characters in a char array, using two bytes (sixteen bits) for each character. Data gathered from many different applications indicates that strings are a major component of heap usage and, moreover, that most String objects contain only Latin-1 characters. Such characters require only one byte of storage, hence half of the space in the internal char arrays of such String objects is going unused.

使用说明:

Description

We propose to change the internal representation of the String class from a UTF-16 char array to a byte array plus an encoding-flag field. The new String class will store characters encoded either as ISO-8859-1/Latin-1 (one byte per character), or as UTF-16 (two bytes per character), based upon the contents of the string. The encoding flag will indicate which encoding is used.

结论:String 再也不用 char[] 来存储啦,改成了 byte[] 加上编码标记,节约了一些空间。

public final class String
 implements java.io.Serializable, Comparable<String>, CharSequence
{
     @Stable
     private final byte[] value;
    ...
}

拓展:StringBuffer 与 StringBuilder

那 StringBuffer 和 StringBuilder 是否仍无动于衷呢?

String-related classes such as AbstractStringBuilder, StringBuilder, and StringBuffer will be updated to use the same representation, as will the HotSpot VM's intrinsic string operations.

JDK11 新特性:新增了一系列字符串处理方法

描述 举例
判断字符串是否为空白 "".isBlank(); // true
去除首尾空白 " Javastack ".strip(); // "Javastack"
去除尾部空格 " Javastack ".stripTrailing(); // " Javastack"
去除首部空格 " Javastack ".stripLeading(); // "Javastack "
复制字符串 "Java".repeat(3);// "JavaJavaJava"
行数统计 "A\nB\nC".lines().count(); // 3

JDK12 新特性:String 实现了 Constable 接口

//String 源码:
public final class String implements java.io.Serializable, Comparable
<String>, CharSequence,Constable, ConstantDesc {
//java.lang.constant.Constable 接口定义了抽象方法:
public interface Constable {
	Optional<? extends ConstantDesc> describeConstable();
}

Java 12 String 的实现源码:

/**
* Returns an {@link Optional} containing the nominal descriptor for 
this
* instance, which is the instance itself.
*
* @return an {@link Optional} describing the {@linkplain String} ins
tance
* @since 12
*/
@Override
public Optional<String> describeConstable() {
	return Optional.of(this);
}

很简单,其实就是调用 Optional.of 方法返回一个 Optional 类型

举例:

private static void testDescribeConstable() {
    String name = "Java 高级工程师";
    Optional<String> optional = name.describeConstable();
    System.out.println(optional.get());
}

结果输出:

Java 高级工程师

JDK12 新特性:String 新增方法

String 的 transform(Function)
var result = "foo".transform(input -> input + " bar");
System.out.println(result); *//foo bar*
或者
var result = "foo".transform(input -> input + " bar").transform(String::toUpperCase)
System.out.println(result); //FOO BAR
//对应的源码:
/**
* This method allows the application of a function to {@code this}
* string. The function should expect a single String argument
* and produce an {@code R} result.
* @since 12
*/
public <R> R transform(Function<? super String, ? extends R> f) {
	return f.apply(this);
}

在某种情况下,该方法应该被称为 map()。

//举例:
private static void testTransform() {
    System.out.println("======test java 12 transform======");
    List<String> list1 = List.of("Java", " Python", " C++ ");
    List<String> list2 = new ArrayList<>();
    list1.forEach(element -> list2.add(element.transform(String::strip)
     .transform(String::toUpperCase)
     .transform((e) -> "Hi," + e))
    );
    list2.forEach(System.out::println);
}s

结果输出:

test java 12 transform

Hi,JAVA

Hi,PYTHON

Hi,C++

如果使用 Java 8 的 Stream 特性,可以如下实现:

private static void testTransform1() {
    System.out.println("======test before java 12 ======");
    List<String> list1 = List.of("Java ", " Python", " C++ ");
    Stream<String> stringStream = list1.stream()
        .map(element -> element.strip())
        .map(String::toUpperCase)
        .map(element -> "Hello," + element);
    List<String> list2 = stringStream.collect(Collectors.toList());
    list2.forEach(System.out::println);
 }

7.3 JDK17:标记删除 Applet API

Applet API 提供了一种将 Java AWT/Swing 控件嵌入到浏览器网页中的方法。不过,目前 Applet 已经被淘汰。大部分人可能压根就没有用过 Applet。Applet API 实际上是无用的,因为所有 Web 浏览器供应商都已删除或透露计划放弃对 Java 浏览器插件的支持。Java 9 的时候,Applet API 已经被标记为过时,Java 17 的时候终于标记为删除了。

具体如下:

java.applet.Applet

java.applet.AppletStub

java.applet.AppletContext

java.applet.AudioClip

javax.swing.JApplet

java.beans.AppletInitializer

8. 其它结构变化

8.1 JDK9:UnderScore(下划线)使用的限制

在 java 8 中,标识符可以独立使用“_”来命名:

String _ = "hello";

System.out.println(_);

但是,在 java 9 中规定“_”不再可以单独命名标识符了,如果使用,会报错:

8.2 JDK11:更简化的编译运行程序

看下面的代码。

javac JavaStack.java // 编译

java JavaStack //运行

我们的认知里,要运行一个 Java 源代码必须先编译,再运行。而在 Java 11 版本中,通过一个 java 命令就直接搞定了,如下所示:

java JavaStack.java

注意点:

执行源文件中的第一个类,第一个类必须包含主方法。

8.3 GC 方面新特性 (JVM相关知识)

GC 是 Java 主要优势之一。 然而,当 GC 停顿太长,就会开始影响应用的响应时间。随着现代系统中内存不断增长,用户和程序员希望 JVM 能够以高效的方式充分利用这些内存, 并且无需长时间的 GC 暂停时间。

8.3.1 G1 GC

JDK9 以后默认的垃圾回收器是 G1GC。

JDK10 : 为 G1 提供并行的 Full GC

G1 最大的亮点就是可以尽量的避免 full gc。但毕竟是“尽量”,在有些情况下,G1 就要进行 full gc 了,比如如果它无法足够快的回收内存的时候,它就会强制停止所有的应用线程然后清理。

在 Java10 之前,一个单线程版的标记-清除-压缩算法被用于 full gc。为了尽量减少 full gc 带来的影响,在 Java10 中,就把之前的那个单线程版的标记-清除-

压缩的 full gc 算法改成了支持多个线程同时 full gc。这样也算是减少了 full gc所带来的停顿,从而提高性能。

你可以通过-XX:ParallelGCThreads 参数来指定用于并行 GC 的线程数。

JDK12:可中断的 G1 Mixed GC

JDK12:增强 G1,自动返回未用堆内存给操作系统

8.3.2 Shenandoah GC

JDK12:Shenandoah GC:低停顿时间的 GC

Shenandoah 垃圾回收器是 Red Hat 在 2014 年宣布进行的一项垃圾收集器研究项目 Pauseless GC 的实现,旨在针对 JVM 上的内存收回实现低停顿的需求。据 Red Hat 研发 Shenandoah 团队对外宣称,Shenandoah 垃圾回收器的暂停时间与堆大小无关,这意味着无论将堆设置为 200 MB 还是 200 GB,

都将拥有一致的系统暂停时间,不过实际使用性能将取决于实际工作堆的大小和工作负载。

Shenandoah GC 主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等。这是一个实验性功能,不包含在默认(Oracle)的 OpenJDK 版本中。

JDK15:Shenandoah 垃圾回收算法转正

Shenandoah 垃圾回收算法终于从实验特性转变为产品特性,这是一个从 JDK 12 引入的回收算法,该算法通过与正在运行的 Java 线程同时进行疏散工作来减少 GC 暂停时间。Shenandoah 的暂停时间与堆大小无关,无论堆栈是 200 MB 还是 200 GB,都具有相同的一致暂停时间。

Shenandoah 在 JDK12 被作为 experimental 引入,在 JDK15 变为 Production;之前需要通过-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC 来启用,现在只需要-XX:+UseShenandoahGC 即可启用。

8.3.3 革命性的 ZGC

JDK11:引入革命性的 ZGC

ZGC,这应该是 JDK11 最为瞩目的特性,没有之一。

ZGC 是一个并发、基于 region、压缩型的垃圾收集器。

ZGC 的设计目标是:支持 TB 级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于 15%。 将来还可以扩展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于 DRAM 和冷对象置于 NVMe 闪存),或压缩堆。

JDK13:ZGC:将未使用的堆内存归还给操作系统

JDK14:ZGC on macOS 和 windows

  • JDK14 之前,ZGC 仅 Linux 才支持。现在 mac 或 Windows 上也能使用 ZGC 了,示例如下:
    • -XX:+UnlockExperimentalVMOptions -XX:+UseZGC
  • ZGC 与 Shenandoah 目标高度相似,在尽可能对吞吐量影响不大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟。

JDK15:ZGC 功能转正

ZGC 是 Java 11 引入的新的垃圾收集器,经过了多个实验阶段,自此终于成为正式特性。

但是这并不是替换默认的 GC,默认的 GC 仍然还是 G1;之前需要通过-XX:+UnlockExperimentalVMOptions、-XX:+UseZGC 来启用 ZGC,现在只需要-XX:+UseZGC 就可以。相信不久的将来它必将成为默认的垃圾回收器。ZGC 的性能已经相当亮眼,用“令人震惊、革命性”来形容,不为过。未来将成为服务端、大内存、低延迟应用的首选垃圾收集器。

怎么形容 Shenandoah 和 ZGC 的关系呢?异同点大概如下:

  • 相同点:性能几乎可认为是相同的
  • 不同点:ZGC 是 Oracle JDK 的,根正苗红。而 Shenandoah 只存在于 OpenJDK 中,因此使用时需注意你的 JDK 版本

JDK16:ZGC 并发线程处理

在线程的堆栈处理过程中,总有一个制约因素就是 safepoints。在 safepoints 这个点,Java 的线程是要暂停执行的,从而限制了 GC 的效率。

回顾:

我们都知道,在之前,需要 GC 的时候,为了进行垃圾回收,需要所有的线程都暂停下来,这个暂停的时间我们称为 Stop The World。而为了实现 STW 这个操作, JVM 需要为每个线程选择一个点停止运行,这个点就叫做安全点(Safepoints)。而 ZGC 的并发线程堆栈处理可以保证 Java 线程可以在 GC safepoints 的同时可以并发执行。它有助于提高所开发的 Java 软件应用程序的性能和效率。

9. 小结与展望

随着云计算和 AI 等技术浪潮,当前的计算模式和场景正在发生翻天覆地的变化,不仅对 Java 的发展速度提出了更高要求,也深刻影响着 Java 技术的发展方向。传统的大型企业或互联网应用,正在被云端、容器化应用、模块化的微服务甚至是函数(FaaS, Function-as-a-Service)所替代。

Java 需要在新的计算场景下,改进开发效率。比如,Java 代码虽然进行了一些类型推断等改进,更易用的集合 API 等,但仍然给开发者留下了过于刻板、形式主义的印象,这是一个长期的改进方向。

Java 虽然标榜面向对象编程,却毫不顾忌的加入面向接口编程思想,又扯出匿名的概念,每增加一个新的东西,对 Java 的根本(面向对象思想)的一次冲击。

只是为了记录自己的学习历程,且本人水平有限,不对之处,请指正。