Java 17 - 现代语言特性的汇合
📑 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 用一行声明表达不可变数据载体:头部列出组件,编译器自动生成构造器、访问器、equals、hashCode 和 toString。适合坐标、金额、DTO 这类"纯数据"类型,替代手写的样板类。
1 | |
record 隐式为 final,字段不可重新赋值;它可以实现接口,但不能继承类。语义上,两个 record 相等当且仅当类型和所有组件都相等。
sealed 密封类
sealed 把继承层次限定在一个已知的集合内:类或接口用 permits 显式列出允许的实现者,集合外的类型无法实现或继承它。这为"有限种类的建模"提供了语言级约束,也让穷尽性检查成为可能。
1 | |
每个被许可的实现者必须声明为 final(不再可扩展)、sealed(继续受限)或 non-sealed(开放扩展)。与 record 组合是常见搭配:代数数据类型既受约束又不可变。
instanceof 模式匹配
instanceof 模式匹配在类型判断的同时绑定模式变量,消除"先判断再强制转换"的重复。模式变量的作用域由流敏感分析决定:仅在判定为真的分支内可用。
1 | |
文本块
文本块用三重引号定义多行字符串,避免拼接转义和换行符。编译器按"公共最小缩进"做规范化:以各行(含结束定界符所在行)中最小的缩进为基准剥离无关注缩进,保留相对结构。
1 | |
注意开头三引号后的换行不计入内容;结束定界符的缩进决定了剥离量,把它放在内容最深处可保留全部前导缩进。
switch 表达式
switch 从语句升级为表达式:可产生值、用箭头标签直达结果、且不再隐式穿透。yield 在代码块分支中返回值,整个表达式必须穷尽所有可能。
1 | |
箭头分支后可直接跟表达式或代码块;代码块内不再需要 break。穷尽性配合密封类型最为自然——所有实现者都被覆盖时无需 default。
JDK 内部 API 强封装
Java 17 将强封装设为默认(JEP 403):sun.misc.Unsafe 等 JDK 内部 API 不再允许非法反射访问,--illegal-access 开关失效。直接依赖内部实现的应用在升级时会收到 InaccessibleObjectException。
1 | |
正确做法是改用官方 API(如 VarHandle、java.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 基线上渐进采用。






