Java 17 (JDK 17) 全面解读:开发者你需要知道的一切新特性
Java 平台作为企业级应用开发的主流选择,一直在持续演进。每两年一次的 LTS (Long-Term Support) 版本发布,更是 Java 生态系统中的重要里程碑。2021 年 9 月发布的 JDK 17,正是这样一个具有重要意义的 LTS 版本。它不仅带来了语言层面的新特性、API 的增强,更有对 JVM 性能的提升、平台支持的扩展以及一些旧有特性的废弃与移除。对于开发者而言,理解和掌握 Java 17 的新特性,是迎接未来挑战、提升开发效率和代码质量的关键。
本文将深入探讨 Java 17 中的主要新特性、改进、废弃和移除,帮助你全面了解这个 LTS 版本,并为升级或使用 Java 17 做好准备。
为什么 JDK 17 如此重要?LTS 的意义
在深入特性细节之前,我们首先需要理解为什么 JDK 17 作为 LTS 版本如此重要。Oracle 每两年发布一个 LTS 版本(如 JDK 8, 11, 17, 21…),而非 LTS 版本则每六个月发布一次。
- 长期支持与稳定性: LTS 版本提供更长时间的维护和支持(通常由 Oracle 提供至少 3 年,由其他 OpenJDK 提供商提供更长时间),这对于企业级应用至关重要,它们需要稳定、可靠的运行环境,并且不希望频繁进行版本升级。
- 生态系统的汇聚点: 大部分第三方库、框架和工具(如 Spring, Maven, Gradle, IDEs)都会优先并且更好地支持 LTS 版本。生态系统的成熟度使得在 LTS 版本上进行开发和部署更加顺畅。
- 企业采用的基石: 许多企业和组织通常会选择一个 LTS 版本作为其主要的开发和生产环境,并在该版本上停留数年。因此,JDK 17 的特性将影响未来几年内大量 Java 应用的开发方式。
正因为是 LTS 版本,JDK 17 融合了自 JDK 11 发布以来多个非 LTS 版本(12, 13, 14, 15, 16)中逐步引入和完善的特性,并将其中一些重要特性定案。
JDK 17 的核心新特性、改进与变化
JDK 17 中包含了大量的 JEP (JDK Enhancement Proposal)。我们将重点关注那些对开发者影响较大、或具有前瞻性的重要特性。
1. 语言特性定案:密封类 (Sealed Classes) – JEP 409
密封类(Sealed Classes)允许你限制哪些其他类或接口可以继承或实现它。这个特性在 JDK 15 和 16 中经历了预览阶段,并在 JDK 17 中最终定案。
核心思想: 通过 sealed
关键字声明一个类或接口是密封的,然后使用 permits
关键字列出允许继承或实现它的类或接口。
为什么需要密封类?
- 更精细的控制继承: 在此之前,你只能选择完全开放继承 (
public
) 或完全禁止继承 (final
),或者在同一个包内开放继承 (默认或protected
)。密封类提供了一个中间地带,允许你在指定的范围内控制继承。 - 改善模式匹配 (Pattern Matching): 密封类与未来版本的
switch
表达式中的模式匹配结合使用时,编译器可以知道所有可能的子类型,从而进行穷举性检查 (exhaustiveness checking),确保你覆盖了所有情况,提高代码的健壮性。 - 更好的数据模型: 适用于表示具有固定、已知变体的领域模型,例如几何图形 (
Shape
permitsCircle
,Rectangle
,Square
)、消息类型 (Message
permitsTextMessage
,ImageMessage
) 等。
语法示例:
“`java
public abstract sealed class Shape
permits Circle, Rectangle, Square {
// … shape methods
}
public final class Circle extends Shape {
// …
}
public non-sealed class Rectangle extends Shape {
// …
}
public sealed class Square extends Shape permits ColoredSquare {
// …
}
public final class ColoredSquare extends Square {
// …
}
// 不允许继承 Shape,除非被 permits 明确列出
// public class Triangle extends Shape { // 编译错误
// // …
// }
“`
注意点:
* permits
后面列出的类必须与密封类在同一个模块或同一个包中。
* 允许继承密封类的子类必须显式声明自己的继承行为:
* final
: 终止继承链。
* sealed
: 继续限制哪些类可以继承它。
* non-sealed
: 变回普通类,允许任何类继承它。
密封类是 Java 迈向更强大、更安全的模式匹配的重要一步,对于构建更清晰、更易维护的数据模型非常有帮助。
2. 预览特性:switch 的模式匹配 (Pattern Matching for switch) – JEP 406 (Preview)
这个特性在 JDK 17 中作为第二次预览引入(第一次在 JDK 16 中)。虽然在 JDK 17 中仍然是预览状态,但其重要性不言而喻,它是对传统 switch
语句/表达式的重大增强。
核心思想: 允许在 switch
的 case
标签中使用模式(如类型模式、卫语句 when
)而不仅仅是常量。
为什么需要它?
- 简化复杂的 instanceof-if 结构: 传统上,处理一个对象的不同类型变体时,我们经常使用一系列
if-else if
结构,结合instanceof
检查和强制类型转换,代码冗长且容易出错。 - 提高代码的可读性和安全性:
switch
模式匹配使得处理多类型逻辑更加清晰,编译器可以进行更严格的检查(如穷举性检查,尤其与密封类结合时),减少运行时错误。
语法示例 (基于 JDK 17 的预览语法,后续版本可能演进):
“`java
// 传统方式
Object obj = …;
if (obj instanceof String) {
String s = (String) obj;
System.out.println(“String: ” + s.length());
} else if (obj instanceof Integer) {
Integer i = (Integer) obj;
System.out.println(“Integer: ” + i * 2);
} else {
System.out.println(“Other type”);
}
// 使用 switch 模式匹配 (JDK 17 预览)
Object obj = …;
switch (obj) {
case String s -> System.out.println(“String: ” + s.length());
case Integer i -> System.out.println(“Integer: ” + i * 2);
case null -> System.out.println(“It’s null!”); // 可以直接处理 null
default -> System.out.println(“Other type”);
}
// 结合卫语句 (Guard Clauses)
switch (obj) {
case String s when s.length() > 5 -> System.out.println(“Long String: ” + s);
case String s -> System.out.println(“Short String: ” + s);
case Integer i when i < 0 -> System.out.println(“Negative Integer: ” + i);
case Integer i -> System.out.println(“Positive or Zero Integer: ” + i);
default -> System.out.println(“Something else”);
}
“`
注意点:
- 在 JDK 17 中使用此特性需要启用预览功能编译和运行。
case null
的处理能力是模式匹配带来的一个便利。- case 标签的顺序很重要,更具体的模式(如带有卫语句的模式)应放在前面。
尽管在 JDK 17 中是预览特性,但它代表了 Java 语言未来发展的重要方向,特别是与密封类的结合,将极大地提升代码的 표현력(expressiveness)和安全性。
3. API 增强:增强的伪随机数生成器 (Enhanced Pseudo-Random Number Generators) – JEP 356
这个 JEP 引入了新的接口和实现,用于提供更高质量、更灵活的伪随机数生成器 (PRNG)。
核心思想: 提供一个新的 java.util.random
包,包含 RandomGenerator
接口及其多种实现,旨在替代和补充旧的 java.util.Random
类。
为什么需要增强?
- 旧 Random 类的局限性:
java.util.Random
功能有限,且在某些场景下性能或随机性质量不佳。 - 标准化和多样化: 存在许多不同的 PRNG 算法,各有优劣(速度、随机性质量、状态空间大小等)。新 API 提供了一个统一的框架来访问这些算法,并允许方便地切换实现。
主要内容:
RandomGenerator
接口:所有新的 PRNG 都实现了这个接口,提供标准的方法来生成各种基本类型(int, long, boolean, float, double)的随机数。RandomGeneratorFactory
类:提供静态方法来发现和创建不同算法的RandomGenerator
实例,例如RandomGeneratorFactory.of("L32Xoshiro256PlusPlus").create()
。- 提供了多种高质量的 PRNG 实现,如 LXM 家族 (LanyRd, L32Xxorwow, L64Xxorwow, L64Xoshiro256PlusPlus) 等,它们在性能和随机性质量方面通常优于
java.util.Random
。 - 兼容性:
java.util.Random
被修改为实现RandomGenerator
接口,保留了向后兼容性。
示例:
“`java
import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;
// 创建一个特定算法的生成器
RandomGenerator random = RandomGeneratorFactory.of(“L64Xoshiro256PlusPlus”).create();
// 使用新的 API 生成随机数
int randomNumber = random.nextInt(100); // 生成 [0, 99] 的随机整数
double randomDouble = random.nextDouble(); // 生成 [0.0, 1.0) 的随机 double
// 使用旧的 Random 类 (现在它也实现了 RandomGenerator)
RandomGenerator oldRandom = new java.util.Random();
int oldRandomNumber = oldRandom.nextInt(100);
“`
新的 PRNG API 提供了更丰富、更灵活的选择,尤其对于需要高质量随机数或特定随机数特性的应用场景(如模拟、游戏、统计分析等)非常有价值。
4. 平台支持:macOS AArch64 Port – JEP 391
这个 JEP 为 macOS 平台提供了对 AArch64 架构的原生支持。
核心思想: 在苹果的 M1 芯片(及后续 M 系列芯片,它们使用 AArch64 架构)上原生运行 JDK。
为什么重要?
- 性能提升: 原生运行比通过 Rosetta 2 模拟 x86_64 架构运行性能更高。
- 完整的平台支持: 使 Java 开发者能够在 M 系列 Mac 上获得与在 Intel Mac 或其他 ARM 平台上一致且优化的开发体验。
这意味着在配备 M1/M2/M3 芯片的 Mac 电脑上安装 JDK 17,你将获得一个针对该架构优化的原生版本,而非模拟版本,从而提高构建、运行和调试 Java 应用的速度。
5. JVM 改进:新的 macOS 渲染管道 – JEP 382
此 JEP 为 macOS 平台引入了一个新的 Java 2D 渲染管道,使用 Apple 的 Metal 图形 API。
核心思想: 替换老旧的 OpenGL 渲染管道。
为什么重要?
- 更好的兼容性和性能: Metal 是 macOS 上更现代、更受支持的图形 API,使用它可以改善 Java Swing/AWT 应用在 macOS 上的渲染性能、稳定性和兼容性。
- 适应平台变化: OpenGL 在 macOS 上已被弃用,转向 Metal 是适应平台技术演进的必要步骤。
对于使用 Swing 或 AWT 开发桌面应用的开发者来说,这个改进意味着他们的应用在 macOS 上的用户体验可能会得到提升。
6. 模块化相关:强力封装 JDK 内部 API (Strongly Encapsulate JDK Internals) – JEP 403
这是 JDK 17 中一个非常重要的、可能影响现有应用的改动。
核心思想: 默认情况下,对 JDK 的所有内部元素进行强力封装,不再允许通过反射等方式随意访问,除非通过命令行选项或模块声明明确开放。
为什么重要?
- 提升安全性和可维护性: 阻止应用程序和库依赖于不稳定的、非标准的 JDK 内部实现细节(如
sun.misc.Unsafe
、sun.security.x509
等)。这些内部 API 可能在未来的 Java 版本中随时改变或移除,导致代码脆弱。 - 促进 JDK 模块化的健康发展: 模块系统的目标之一就是明确 JDK 各部分的边界,强力封装是实现这一目标的关键步骤。
具体变化:
- 在 JDK 11 中,
--illegal-access=permit
是默认值,允许访问内部 API 并发出警告。 - 在 JDK 17 中,
--illegal-access=deny
成为默认值。这意味着如果没有明确开放,访问 JDK 内部 API 将直接抛出IllegalAccessException
或InaccessibleObjectException
。 - 在 JDK 16 及更早版本中,可以通过
--illegal-access=deny
提前测试这种行为。
对现有应用的影响:
- 如果你的应用或其依赖库使用了 JDK 的内部 API,那么在 JDK 17 上默认运行时可能会失败。
- 解决方案: 对于确实需要访问某些内部 API 的情况(通常是第三方库),你需要使用
--add-opens
命令行选项来显式开放特定的包。例如:--add-opens java.base/sun.security.x509=ALL-UNNAMED
。或者,如果你的代码在使用模块系统,可以在module-info.java
中使用opens
或requires transitive
等指令。 - 长期建议: 尽量避免依赖 JDK 内部 API,寻找标准的、公共的替代方案。对于第三方库的依赖,检查它们是否有支持 JDK 17 或更高版本的更新版本。
JEP 403 是 Java 平台走向更加安全、稳定和模块化的必然一步,虽然可能给迁移带来一些挑战,但从长远来看是有益的。
7. 废弃和移除:Java 的瘦身与方向调整
JDK 17 也废弃和移除了一些不再推荐或过时的特性,这有助于简化 JDK、降低维护成本,并指明平台未来的发展方向。
-
废弃安全管理器 (Security Manager) – JEP 411 (Deprecated for Removal)
- 原因: 安全管理器在设计上存在固有的复杂性和维护困难,且在现代应用部署模式(如容器化、微服务)中不再像 Applet 时代那样普遍有效。许多现代安全需求通过操作系统级别的权限、沙箱技术、代码签名等方式实现。
- 影响:
java.lang.SecurityManager
类及其相关 API 被标记为废弃,并计划在未来的版本中移除。 - 建议: 依赖安全管理器的应用需要寻找替代的安全模型。
-
废弃 Applet API – JEP 398 (Deprecated for Removal)
- 原因: Applet 技术早已被 Web 浏览器广泛放弃,并且存在安全风险。
- 影响:
java.applet
包下的所有 API 被标记为废弃,并计划在未来的版本中移除。 - 建议: Web 应用应使用现代 Web 技术替代 Applet。
-
移除 RMI 激活机制 (Remove RMI Activation) – JEP 407
- 原因: RMI (Remote Method Invocation) 的激活机制是一个复杂且很少使用的部分。
- 影响:
java.rmi.activation
包下的类被彻底移除。核心 RMI 功能不受影响。
-
移除实验性的 AOT 和 JIT 编译器 (Remove the Experimental AOT and JIT Compiler) – JEP 410
- 原因: 这些实验性功能(基于 GraalVM 的实验性提前编译 AOT 和实验性 JIT)没有得到广泛使用,并且维护成本较高。
- 影响:
jaotc
工具和相关的 JVM 选项被移除。请注意,这是指 实验性 的 AOT 和 JIT 选项,与 HotSpot 默认的 JIT 编译器无关。GraalVM 作为一个独立的 JDK 发行版,其 AOT 编译能力(Native Image)仍然是活跃发展的。
这些废弃和移除信号着 Java 平台正在清理历史包袱,聚焦于更现代、更具活力的技术领域。
8. 孵化器模块:未来方向的探索
孵化器模块 (Incubator Modules) 允许在 JDK 中包含非最终的 API 和工具,让开发者可以提前体验和提供反馈。这些特性尚未稳定,未来可能发生变化甚至被移除。
-
外部函数和内存 API (Foreign Function and Memory API) – JEP 412 (Second Incubator)
- 目标: 提供一个更安全、更高效、更易于使用的替代 JNI (Java Native Interface) 的方案,用于 Java 代码访问外部库(即调用原生代码)和非 JVM 管理的内存。
- 为什么重要? JNI 使用复杂且不安全(容易导致 JVM 崩溃),外部函数和内存 API (FFM API) 旨在解决这些问题,为与原生代码互操作提供更好的抽象。
- 状态: 在 JDK 17 中是第二次孵化,API 还在演进中(后续版本持续改进)。
-
向量 API (Vector API) – JEP 414 (Second Incubator)
- 目标: 提供一个 API 来表示向量计算,这些计算可以在运行时编译为优化的向量指令(如 SIMD – Single Instruction, Multiple Data)集。
- 为什么重要? SIMD 指令可以显著加速某些类型的计算,如数值计算、图像处理、机器学习等。Vector API 旨在让 Java 代码能够安全可靠地利用这些底层硬件能力。
- 状态: 在 JDK 17 中是第二次孵化,API 还在演进中(后续版本持续改进)。
这些孵化器模块是 Project Panama 的一部分,它们代表着 Java 在高性能计算和原生互操作领域的未来发展方向,非常值得关注。
升级到 JDK 17 的考虑
- 兼容性: 最主要的挑战可能是 JEP 403 带来的强力封装。你需要测试你的应用和所有依赖库在 JDK 17 默认设置下的兼容性。如果遇到
IllegalAccessException
或InaccessibleObjectException
,可能需要--add-opens
选项,或者升级依赖库。 - 废弃和移除的特性: 检查你的代码是否使用了被废弃或移除的特性(如 Applet, RMI Activation, Security Manager)。虽然 JDK 17 只是废弃了 Security Manager 和 Applet API,但如果你的代码广泛依赖它们,需要提前规划迁移方案。
- 新特性的利用: 评估哪些新特性(如密封类、增强的 PRNG)可以改进你的代码设计和效率。预览特性 (Pattern Matching for switch, FFM API, Vector API) 可以在非生产环境尝试,但不推荐在生产环境中使用,因为它们可能在未来版本中发生变化。
- 构建工具和 IDE 支持: 确保你使用的构建工具(Maven, Gradle)和 IDE (IntelliJ IDEA, Eclipse, VS Code) 完全支持 JDK 17。大多数现代版本的工具都已提供良好支持。
总结
Java 17 作为一个 LTS 版本,是 Java 平台发展历程中的一个重要节点。它不仅带来了语言层面的新结构(密封类),改进了核心 API(增强的 PRNG),增强了平台支持(macOS AArch64, Metal 渲染),更通过强力封装 JDK 内部 API、废弃和移除旧有特性等方式,塑造了 Java 平台未来的形态。同时,通过孵化器模块(FFM API, Vector API)展示了未来在高性能计算和原生互操作领域的潜力。
对于 Java 开发者而言,拥抱 JDK 17 是一个必然的趋势。无论是为了享受 LTS 带来的稳定性、提升开发效率,还是为了适应平台的变化、利用新的技术能力,深入了解并逐步迁移到 JDK 17 都是一个值得投入精力的事情。
希望本文能够帮助你全面了解 Java 17 的关键特性,并为你的开发实践提供指导。