---
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 每个版本的更新带来的新特性数量和更新时间：

![ JDK 8 到 JDK 25 每个版本的更新带来的新特性数量和更新时间](https://oss.javaguide.cn/github/javaguide/java/new-features/jdk8~jdk24.png)

### 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 每个版本的更新带来新特性数量和更新时间：

![](https://oss.javaguide.cn/github/javaguide/java/new-features/jdk8~jdk24.png)

### 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 {
}
```

![](https://oss.javaguide.cn/javaguide/image-20210820153955587.png)

如果允许扩展的子类和封闭类在同一个源代码文件里，封闭类可以不使用 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 -->
