📑 Java 演进系列总览 | Java 8 | Java 11 | Java 17 | Java 21 | Java 25

Java 17 于 2021 年 9 月 14 日正式发布,是继 Java 11 之后又一个 LTS 版本,也是现代 Java 语言特性的汇合点。需要注意:record(Java 16 定稿)、文本块(Java 15 定稿)、switch 表达式(Java 14 定稿)和 instanceof 模式匹配(Java 16 定稿)并非由 Java 17 首次引入,它们在此 LTS 中可用;本文将其一并收录,是因为 Java 17 是这些特性的首个 LTS 基线。密封类(JEP 409)和 JDK 内部 API 强封装(JEP 403)则是在 Java 17 中定稿的。

record 记录类

record 用一行声明表达不可变数据载体:头部列出组件,编译器自动生成构造器、访问器、equalshashCodetoString。适合坐标、金额、DTO 这类"纯数据"类型,替代手写的样板类。

1
2
3
4
5
6
7
8
9
10
11
12
public record Point(int x, int y) {
// 可声明紧凑构造器做参数校验
public Point {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("负坐标");
}
}
}

Point p = new Point(3, 4);
int x = p.x(); // 访问器按组件名生成
// p.equals(new Point(3, 4)) 为 true

record 隐式为 final,字段不可重新赋值;它可以实现接口,但不能继承类。语义上,两个 record 相等当且仅当类型和所有组件都相等。

sealed 密封类

sealed 把继承层次限定在一个已知的集合内:类或接口用 permits 显式列出允许的实现者,集合外的类型无法实现或继承它。这为"有限种类的建模"提供了语言级约束,也让穷尽性检查成为可能。

1
2
3
4
5
6
7
8
9
public sealed interface Shape
permits Circle, Square {}

// 实现者必须是 final、sealed 或 non-sealed
public record Circle(double radius) implements Shape {}
public record Square(double side) implements Shape {}

// 编译错误:Triangle 不在 permits 列表中
// record Triangle(...) implements Shape {}

每个被许可的实现者必须声明为 final(不再可扩展)、sealed(继续受限)或 non-sealed(开放扩展)。与 record 组合是常见搭配:代数数据类型既受约束又不可变。

instanceof 模式匹配

instanceof 模式匹配在类型判断的同时绑定模式变量,消除"先判断再强制转换"的重复。模式变量的作用域由流敏感分析决定:仅在判定为真的分支内可用。

1
2
3
4
5
6
7
8
9
Object obj = "hello";

// 旧写法需要两步
// if (obj instanceof String) { String s = (String) obj; ... }

if (obj instanceof String s && !s.isBlank()) {
System.out.println(s.length()); // 5
}
// s 在此处不可见,作用域限定在真分支内

文本块

文本块用三重引号定义多行字符串,避免拼接转义和换行符。编译器按"公共最小缩进"做规范化:以各行(含结束定界符所在行)中最小的缩进为基准剥离无关注缩进,保留相对结构。

1
2
3
4
5
6
7
8
String json = """
{
"name": "Java",
"version": 17
}
""";
// 结果首行是 {,内部保留两空格缩进
// 行尾反斜杠 \ 续行,\s 显式保留尾随空格

注意开头三引号后的换行不计入内容;结束定界符的缩进决定了剥离量,把它放在内容最深处可保留全部前导缩进。

switch 表达式

switch 从语句升级为表达式:可产生值、用箭头标签直达结果、且不再隐式穿透。yield 在代码块分支中返回值,整个表达式必须穷尽所有可能。

1
2
3
4
5
6
7
8
9
int days = switch (month) {
case 1, 3, 5, 7, 8, 10, 12 -> 31;
case 4, 6, 9, 11 -> 30;
case 2 -> isLeap ? 29 : 28;
default -> {
// 块分支中用 yield 返回
yield 0;
}
};

箭头分支后可直接跟表达式或代码块;代码块内不再需要 break。穷尽性配合密封类型最为自然——所有实现者都被覆盖时无需 default

JDK 内部 API 强封装

Java 17 将强封装设为默认(JEP 403):sun.misc.Unsafe 等 JDK 内部 API 不再允许非法反射访问,--illegal-access 开关失效。直接依赖内部实现的应用在升级时会收到 InaccessibleObjectException

1
2
3
4
5
6
# 运行时显式开放指定内部包
java --add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.util=ALL-UNNAMED App

# 迁移前先扫描内部 API 依赖
jdeps --jdk-internals app.jar

正确做法是改用官方 API(如 VarHandlejava.lang.invoke)替代 Unsafe;短期无法重构时用 --add-opens 定向开放。模块系统层面的背景见 Java 模块化

常用特性总结

  • record:一行声明不可变数据载体,自动生成构造器、访问器与相等语义
  • sealed + permits:限定继承层次,配合穷尽性检查建模有限类型集合
  • instanceof 模式匹配:判断与绑定合一,模式变量作用域流敏感
  • 文本块:三引号多行字符串,按公共最小缩进规范化
  • switch 表达式:产生值、箭头标签、yield 返回、强制穷尽
  • 强封装:JDK 内部 API 默认不可反射访问,用 --add-opens 过渡

迁移与兼容性

从 Java 8/11 升级到 Java 17 的主要障碍是强封装:老框架和字节码增强库(如早期 ASM、CGLIB)会因访问内部 API 而失败,先运行 jdeps --jdk-internals 评估,再决定升级依赖版本或临时使用 --add-opens。被移除的 API(如 Nashorn、Java EE 模块)需要提前替换实现。语言层面则几乎全是增量:新特性不影响存量代码,可直接在 Java 17 基线上渐进采用。

参考资料