324 lines
12 KiB
Markdown
324 lines
12 KiB
Markdown
---
|
||
title: Java 14 & 15 新特性概览
|
||
description: 概览 JDK 14/15 的关键特性,如 record、文本块与空指针精准提示等语言增强。
|
||
category: Java
|
||
tag:
|
||
- Java新特性
|
||
head:
|
||
- - meta
|
||
- name: keywords
|
||
content: Java 14,Java 15,record,文本块,NullPointerException 细节,模式匹配,JEP
|
||
---
|
||
|
||
## Java 14
|
||
|
||
JDK 14 于 2020 年 3 月 17 日发布,这是一个非 LTS 版本。
|
||
|
||
这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||
|
||
- [JEP 305: Pattern Matching for instanceof(instanceof 模式匹配,预览)](https://openjdk.org/jeps/305)
|
||
- [JEP 358: Helpful NullPointerExceptions (空指针异常精准提示)](https://openjdk.org/jeps/358)
|
||
- [JEP 361: Switch Expressions (Standard) (switch 表达式, 转正)](https://openjdk.org/jeps/361)
|
||
- [JEP 359: Records (Preview) (record 关键字, 预览特性)](https://openjdk.org/jeps/359)
|
||
- [JEP 368: Text Blocks (Second Preview) (文本块, 第二次预览)](https://openjdk.org/jeps/368)
|
||
- [JEP 363: Remove the CMS Garbage Collector (移除 CMS 垃圾收集器)](https://openjdk.org/jeps/363)
|
||
|
||
下图是从 JDK 8 到 JDK 25 每个版本的更新带来的新特性数量和更新时间:
|
||
|
||

|
||
|
||
### JEP 305: Pattern Matching for instanceof(instanceof 模式匹配,预览)
|
||
|
||
Java 14 继续将 instanceof 模式匹配作为预览特性,这是 Java 14 引入的功能(JEP 305)。
|
||
|
||
该特性允许在 instanceof 检查的同时进行类型转换,避免了显式强制转换的需要。注意:instanceof 模式匹配在 Java 14 是第一次预览(JEP 305),在 Java 15 是第二次预览(JEP 375),最终在 Java 16 转正(JEP 394)。
|
||
|
||
### JEP 358: Helpful NullPointerExceptions(空指针异常精准提示)
|
||
|
||
通过 JVM 参数中添加 `-XX:+ShowCodeDetailsInExceptionMessages`,可以在空指针异常中获取更为详细的调用信息,更快的定位和解决问题。
|
||
|
||
```java
|
||
a.b.c.i = 99; // 假设这段代码会发生空指针
|
||
```
|
||
|
||
Java 14 之前:
|
||
|
||
```java
|
||
Exception in thread "main" java.lang.NullPointerException
|
||
at NullPointerExample.main(NullPointerExample.java:5)
|
||
```
|
||
|
||
Java 14 之后:
|
||
|
||
```java
|
||
// 增加参数后提示的异常中很明确的告知了哪里为空导致
|
||
Exception in thread "main" java.lang.NullPointerException:
|
||
Cannot read field 'c' because 'a.b' is null.
|
||
at Prog.main(Prog.java:5)
|
||
```
|
||
|
||
### JEP 361: Switch Expressions(switch 表达式,标准版)
|
||
|
||
Java 12 引入的 switch(预览特性)在 Java 14 变为正式版本,不需要增加参数来启用,直接在 JDK 14 中就能使用。
|
||
|
||
Java 12 为 switch 表达式引入了类似 lambda 语法条件匹配成功后的执行块,不需要多写 break,Java 13 提供了 `yield` 来在 block 中返回值。
|
||
|
||
```java
|
||
String result = switch (day) {
|
||
case "M", "W", "F" -> "MWF";
|
||
case "T", "TH", "S" -> "TTS";
|
||
default -> {
|
||
if(day.isEmpty())
|
||
yield "Please insert a valid day.";
|
||
else
|
||
yield "Looks like a Sunday.";
|
||
}
|
||
|
||
};
|
||
System.out.println(result);
|
||
```
|
||
|
||
### JEP 359: Records(record 类,预览)
|
||
|
||
`record` 关键字可以简化 **数据类**(一个 Java 类一旦实例化就不能再修改)的定义方式,使用 `record` 代替 `class` 定义的类,只需要声明属性,就可以在获得属性的访问方法,以及 `toString()`,`hashCode()`, `equals()` 方法。
|
||
|
||
类似于使用 `class` 定义类,同时使用了 lombok 插件,并打上了 `@Getter,@ToString,@EqualsAndHashCode` 注解。
|
||
|
||
```java
|
||
/**
|
||
* 这个类具有两个特征
|
||
* 1. 所有成员属性都是final
|
||
* 2. 全部方法由构造方法,和两个成员属性访问器组成(共三个)
|
||
* 那么这种类就很适合使用record来声明
|
||
*/
|
||
final class Rectangle implements Shape {
|
||
final double length;
|
||
final double width;
|
||
|
||
public Rectangle(double length, double width) {
|
||
this.length = length;
|
||
this.width = width;
|
||
}
|
||
|
||
double length() { return length; }
|
||
double width() { return width; }
|
||
}
|
||
/**
|
||
* 1. 使用 record 声明的类会自动拥有上面类中的三个方法
|
||
* 2. 在这基础上还附赠了 equals(),hashCode() 方法以及 toString() 方法
|
||
* 3. toString 方法中包括所有成员属性的字符串表示形式及其名称
|
||
*/
|
||
record Rectangle(float length, float width) { }
|
||
```
|
||
|
||
### JEP 368: Text Blocks(文本块,第二次预览)
|
||
|
||
Java14 中,文本块依然是预览特性,不过,其引入了两个新的转义字符:
|
||
|
||
- `\` : 表示行尾,不引入换行符
|
||
- `\s`:表示单个空格
|
||
|
||
```java
|
||
String str = "凡心所向,素履所往,生如逆旅,一苇以航。";
|
||
|
||
String str2 = """
|
||
凡心所向,素履所往, \
|
||
生如逆旅,一苇以航。""";
|
||
System.out.println(str2);// 凡心所向,素履所往, 生如逆旅,一苇以航。
|
||
String text = """
|
||
java
|
||
c++\sphp
|
||
""";
|
||
System.out.println(text);
|
||
//输出:
|
||
java
|
||
c++ php
|
||
```
|
||
|
||
### 其他特性
|
||
|
||
- 从 Java11 引入的 ZGC 作为继 G1 过后的下一代 GC 算法,从支持 Linux 平台到 Java14 开始支持 MacOS 和 Windows(个人感觉是终于可以在日常开发工具中先体验下 ZGC 的效果了,虽然其实 G1 也够用)
|
||
- [JEP 363: Remove the CMS Garbage Collector](https://openjdk.org/jeps/363)(移除 CMS 垃圾收集器,功成而退)
|
||
- 新增了 jpackage 工具,标配将应用打成 jar 包外,还支持不同平台的特性包,比如 linux 下的 `deb` 和 `rpm`,window 平台下的 `msi` 和 `exe`
|
||
|
||
## Java 15
|
||
|
||
JDK 15 于 2020 年 9 月 15 日发布,这是一个非 LTS 版本。
|
||
|
||
这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||
|
||
- [JEP 379: Shenandoah: A Low-Pause-Time Garbage Collector (Shenandoah GC, 转正)](https://openjdk.org/jeps/379)
|
||
- [JEP 371: Hidden Classes (隐藏类)](https://openjdk.org/jeps/371)
|
||
- [JEP 378: Text Blocks (Standard) (文本块, 转正)](https://openjdk.org/jeps/378)
|
||
- [JEP 360: Sealed Classes (Preview) (密封类, 预览特性)](https://openjdk.org/jeps/360)
|
||
- [JEP 339: EdDSA (数字签名算法)](https://openjdk.org/jeps/339)
|
||
|
||
下图是从 JDK 8 到 JDK 24 每个版本的更新带来新特性数量和更新时间:
|
||
|
||

|
||
|
||
### JEP 379: Shenandoah(Shenandoah GC,标准版)
|
||
|
||
Shenandoah 垃圾收集器从 Java 12 开始作为实验性特性引入,经过多个版本的迭代和完善,在 Java 15 终于转正为正式特性。
|
||
|
||
Shenandoah 是 Red Hat 主导开发的低延迟垃圾收集器,主要目标是:
|
||
|
||
- 99.9% 的 GC 停顿时间小于 10ms
|
||
- 停顿时间与堆大小无关
|
||
- 支持从几百 MB 到几 TB 的堆内存
|
||
|
||
与 G1 和 ZGC 不同,Shenandoah 采用并发标记-整理算法,可以在不停止应用线程的情况下进行垃圾回收。
|
||
|
||
启用 Shenandoah GC:
|
||
|
||
```bash
|
||
java -XX:+UseShenandoahGC className
|
||
```
|
||
|
||
### JEP 339: EdDSA(数字签名算法)
|
||
|
||
新加入了一个安全性和性能都更强的基于 Edwards-Curve Digital Signature Algorithm(EdDSA)实现的数字签名算法。
|
||
|
||
虽然其性能优于现有的 ECDSA 实现,不过,它并不会完全取代 JDK 中现有的椭圆曲线数字签名算法( ECDSA)。
|
||
|
||
```java
|
||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519");
|
||
KeyPair kp = kpg.generateKeyPair();
|
||
|
||
byte[] msg = "test_string".getBytes(StandardCharsets.UTF_8);
|
||
|
||
Signature sig = Signature.getInstance("Ed25519");
|
||
sig.initSign(kp.getPrivate());
|
||
sig.update(msg);
|
||
byte[] s = sig.sign();
|
||
|
||
String encodedString = Base64.getEncoder().encodeToString(s);
|
||
System.out.println(encodedString);
|
||
```
|
||
|
||
输出:
|
||
|
||
```plain
|
||
0Hc0lxxASZNvS52WsvnncJOH/mlFhnA8Tc6D/k5DtAX5BSsNVjtPF4R4+yMWXVjrvB2mxVXmChIbki6goFBgAg==
|
||
```
|
||
|
||
### JEP 378: Text Blocks(文本块,标准版)
|
||
|
||
在 Java 15,文本块终于转正为正式功能特性,不再需要 `--enable-preview` 参数即可使用。
|
||
|
||
文本块提供了一种更简洁的方式来定义多行字符串,特别适合用于 SQL、JSON、HTML 等场景。
|
||
|
||
### JEP 371: Hidden Classes(隐藏类)
|
||
|
||
隐藏类是为框架(frameworks)所设计的,支持动态生成的类,这些类不能直接被其他类的字节码使用,只能在运行时通过反射间接使用它们。
|
||
|
||
主要特点:
|
||
|
||
- 不可被发现:隐藏类不能被其他类直接依赖
|
||
- 生命周期短:通常在使用完毕后就会被卸载
|
||
- 性能优化:为动态语言运行时提供了更好的性能支持
|
||
|
||
适用场景:
|
||
|
||
- 动态语言运行时(如 JavaScript、Groovy)
|
||
- 字节码生成框架(如 ASM、CGLIB)
|
||
- 反射代理(如动态代理)
|
||
|
||
### JEP 360: Sealed Classes(密封类,预览)
|
||
|
||
**密封类(Sealed Classes)** 是 Java 15 中的一个预览新特性。
|
||
|
||
没有密封类之前,在 Java 中如果想让一个类不能被继承和修改,我们可以使用 `final` 关键字对类进行修饰。不过,这种方式不太灵活,直接把一个类的继承和修改渠道给堵死了。
|
||
|
||
密封类可以对继承或者实现它们的类进行限制,这样这个类就只能被指定的类继承。
|
||
|
||
```java
|
||
// 抽象类 Person 只允许 Employee 和 Manager 继承。
|
||
public abstract sealed class Person
|
||
permits Employee, Manager {
|
||
|
||
//...
|
||
}
|
||
```
|
||
|
||
另外,任何扩展密封类的类本身都必须声明为 `sealed`、`non-sealed` 或 `final`。
|
||
|
||
```java
|
||
public final class Employee extends Person {
|
||
}
|
||
|
||
public non-sealed class Manager extends Person {
|
||
}
|
||
```
|
||
|
||

|
||
|
||
如果允许扩展的子类和封闭类在同一个源代码文件里,封闭类可以不使用 permits 语句,Java 编译器将检索源文件,在编译期为封闭类添加上许可的子类。
|
||
|
||
### JEP 375: Pattern Matching for instanceof(instanceof 模式匹配,第二次预览)
|
||
|
||
Java 15 继续将 instanceof 模式匹配作为预览特性,没有进行重大调整,主要是为了收集更多使用反馈。
|
||
|
||
instanceof 模式匹配允许在类型检查的同时进行变量绑定,避免了显式类型转换,使代码更简洁安全。
|
||
|
||
示例:
|
||
|
||
```java
|
||
// 传统写法
|
||
if (obj instanceof String) {
|
||
String str = (String) obj;
|
||
System.out.println(str.length());
|
||
}
|
||
|
||
// 模式匹配写法
|
||
if (obj instanceof String str) {
|
||
System.out.println(str.length());
|
||
}
|
||
```
|
||
|
||
### API 增强
|
||
|
||
#### CharSequence 增强
|
||
|
||
在 Java 15 之前,如果你想判断一个 `StringBuilder`、`StringBuffer` 或者 `CharBuffer` 是否为空,通常需要调用 `length() == 0`。
|
||
|
||
`CharSequence` 接口添加了一个默认方法 `isEmpty()` 来判断字符序列为空,如果是则返回 true。
|
||
|
||
```java
|
||
public interface CharSequence {
|
||
default boolean isEmpty() {
|
||
return this.length() == 0;
|
||
}
|
||
}
|
||
```
|
||
|
||
由于 `String`、`StringBuilder` 等都实现了这个接口,现在你可以用更统一、更具语义化的方式来编写判断逻辑。这对于编写泛型代码(处理各种字符序列)非常友好。
|
||
|
||
**注意:** `String` 类虽然早已有了 `isEmpty()` 方法,但那个是 `String` 自己定义的;Java 15 这一步是将该能力“上浮”到了接口层。
|
||
|
||
#### TreeMap 性能提升
|
||
|
||
这是一个非常硬核的优化。虽然 `putIfAbsent()`、`compute()` 等方法早在 Java 8 就出现在 `Map` 接口中了,但在 Java 15 之前,`TreeMap` 并没有自己去实现它们,而是直接沿用接口里的 `default` 实现。
|
||
|
||
**Java 15 的变化**:`TreeMap` 专门重写了以下方法:
|
||
|
||
- `putIfAbsent()`
|
||
- `computeIfAbsent()`
|
||
- `computeIfPresent()`
|
||
- `compute()`
|
||
- `merge()`
|
||
|
||
**为什么要重写?**
|
||
|
||
- **性能提升**:接口里的默认实现通常比较“笨”,往往需要进行多次查找(例如先查找是否存在,再决定是否插入)。
|
||
- **O(log n) 保证**:`TreeMap` 优化后的实现可以将查找和插入合并在一次红黑树遍历中完成,避免了重复的树搜索,显著提升了在处理大规模数据时的执行效率。
|
||
|
||
### 其他特性
|
||
|
||
- **Nashorn JavaScript 引擎彻底移除**:Nashorn 从 Java8 开始引入的 JavaScript 引擎,Java9 对 Nashorn 做了些增强,实现了一些 ES6 的新特性。在 Java 11 中就已经被弃用,到了 Java 15 就彻底被删除了。
|
||
- **DatagramSocket API 重构**
|
||
- **禁用和废弃偏向锁(Biased Locking)**:偏向锁的引入增加了 JVM 的复杂性大于其带来的性能提升。不过,你仍然可以使用 `-XX:+UseBiasedLocking` 启用偏向锁定,但它会提示这是一个已弃用的 API。
|
||
- ……
|
||
|
||
<!-- @include: @article-footer.snippet.md -->
|