最近在项目升级中,很多同学都在问:JDK 17 到底有哪些值得关注的新特性?网上的资料要么太零散,要么只讲语法不讲实战。作为长期支持版本(LTS),JDK 17 带来的不仅是语法糖,更有能直接提升开发效率和代码质量的生产力工具。本文将带你用30分钟,系统梳理JDK 17的核心新特性,从密封类到模式匹配,从新API到性能提升,每个特性都配有可运行的代码示例和工程实践建议,帮你快速掌握升级要点,无论是学习还是项目迁移都能直接参考。
1. 背景与核心概念:为什么是 JDK 17?
在深入特性之前,我们需要理解 JDK 17 的定位。自 JDK 9 引入模块化以来,Java 进入了每半年发布一个版本的快速迭代周期。为了平衡创新与稳定,Oracle 定义了长期支持版本(Long-Term Support, LTS)。JDK 11 和 JDK 17 就是这样的 LTS 版本,意味着它们会获得数年的官方支持和更新,是企业级应用升级的首选。
JDK 17 解决了什么问题?
- 语言表达力增强:引入了如密封类(Sealed Classes)、模式匹配(Pattern Matching)等特性,让开发者能用更简洁、更安全的代码来表达复杂的业务逻辑和领域模型。
- 性能与效率提升:包括新的 API(如新的随机数生成器)、底层优化(如向量API孵化)以及垃圾回收器的持续改进(如 ZGC 和 Shenandoah 成为正式特性),旨在降低延迟、提升吞吐量。
- 安全与维护性:强封装 JDK 内部 API,推动开发者使用标准 API,增强了应用的安全性。同时,移除了一些过时的 API 和功能,减少了技术债务。
常见应用场景:
- 新项目技术选型:对于即将启动的微服务、后端系统,直接选择 JDK 17 LTS 作为基础运行时,能充分利用现代 Java 特性。
- 老系统升级评估:从 JDK 8 或 JDK 11 升级时,需要评估新特性的兼容性与收益,JDK 17 是重要的目标版本。
- 提升代码质量:利用新特性重构现有代码,使其更健壮、更易读。
接下来,我们将在一个统一的环境下,逐一拆解这些特性。
2. 环境准备与版本说明
为了确保示例代码能够顺利运行,你需要准备以下环境:
- 操作系统:Windows 10/11, macOS, 或主流 Linux 发行版(如 Ubuntu 20.04+)。
- JDK 版本:OpenJDK 17或Oracle JDK 17。本文所有示例基于 OpenJDK 17.0.2 测试。
- 下载:建议从 Adoptium 或 Oracle官网 下载。
- 构建工具:示例使用 Maven 或直接使用
javac/java命令。确保 Maven 版本为 3.6+。 - IDE:IntelliJ IDEA (2021.2+)、 Eclipse (2021-09+) 或 VS Code 配合 Java 扩展包均可,它们对 JDK 17 新语法有良好支持。
验证安装: 打开终端或命令提示符,运行以下命令:
java -version预期输出应包含17,例如:
openjdk version "17.0.2" 2022-01-18 OpenJDK Runtime Environment (build 17.0.2+8-86) OpenJDK 64-Bit Server VM (build 17.0.2+8-86, mixed mode, sharing)示例项目结构: 我们将创建一个简单的 Maven 项目来演示所有特性。
<!-- pom.xml --> <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>jdk17-features-demo</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> </project>3. 核心特性精讲与实战
3.1 密封类 (Sealed Classes) - JEP 409
是什么:密封类是一种限制哪些其他类或接口可以继承或实现它的类或接口。它明确规定了“谁可以成为我的子类”。
解决什么问题:在传统的继承体系中,一个类可以被任意其他类继承,这可能导致在switch表达式或if-else链中无法穷尽所有子类类型,影响代码的可靠性和可维护性。密封类通过编译时检查,确保了层次结构的封闭性和可控性。
核心语法:
- 使用
sealed关键字修饰类或接口。 - 使用
permits子句明确指定允许继承的子类。 - 子类必须是
final,sealed, 或non-sealed之一。
完整示例:模拟一个图形系统。
// 文件路径:src/main/java/com/example/sealed/Shape.java package com.example.sealed; // 1. 定义密封类 Shape,只允许 Circle 和 Rectangle 继承 public sealed class Shape permits Circle, Rectangle { public abstract double area(); } // 2. Circle 是一个 final 类,不能再被继承 final class Circle extends Shape { private final double radius; public Circle(double radius) { this.radius = radius; } @Override public double area() { return Math.PI * radius * radius; } } // 3. Rectangle 是一个 non-sealed 类,可以被任意继承 non-sealed class Rectangle extends Shape { protected double length, width; public Rectangle(double length, double width) { this.length = length; this.width = width; } @Override public double area() { return length * width; } } // 4. Square 继承自 non-sealed 的 Rectangle class Square extends Rectangle { public Square(double side) { super(side, side); } } // 5. 尝试继承 Shape 但不被 permits 的类会导致编译错误 // class Triangle extends Shape {} // 编译错误: Shape 不允许 Triangle 继承为什么这样做:
final:表示继承链到此为止。non-sealed:重新开放继承,用于你希望部分分支可扩展的情况。sealed:子类本身也是密封的,可以继续控制自己的子类。
工程实践:
- 领域建模:在定义核心领域对象(如订单状态、支付类型)时使用,确保类型的完备性。
- 配合模式匹配:与
switch表达式结合,编译器可以检查是否覆盖了所有许可的子类,避免运行时错误。
3.2 模式匹配 for instanceof (第二次预览) - JEP 406
是什么:简化instanceof检查和类型转换的语法糖。在 JDK 16 中成为正式特性,JDK 17 对其进行了增强和巩固。
语法对比:
// 传统写法 Object obj = "Hello, Pattern Matching!"; if (obj instanceof String) { String str = (String) obj; // 需要显式强制转换 System.out.println(str.toUpperCase()); } // JDK 16+ 模式匹配写法 if (obj instanceof String str) { // 直接声明模式变量 str System.out.println(str.toUpperCase()); // str 在此作用域内可直接使用 }优势:
- 代码更简洁:将类型判断、类型转换和变量声明合并为一步。
- 更安全:避免了因疏忽而错误的强制转换。
- 作用域清晰:模式变量
str的作用域仅限于if块内。
进阶用法:结合&&进行条件判断。
Object obj = 42; if (obj instanceof Integer i && i > 0) { System.out.println("正数: " + i); }3.3 模式匹配 for switch (预览) - JEP 406
是什么:将模式匹配的能力扩展到switch表达式和语句中,允许在case标签中使用类型模式。
注意:此特性在 JDK 17 中仍是预览特性,需要使用--enable-preview编译和运行。
编译与运行:
# 编译 javac --enable-preview --release 17 Main.java # 运行 java --enable-preview Main示例代码:利用密封类和模式匹配switch,实现安全、完备的类型处理。
// 文件路径:src/main/java/com/example/pattern/PatternSwitchDemo.java package com.example.pattern; import com.example.sealed.*; public class PatternSwitchDemo { public static String describeShape(Shape shape) { // 使用模式匹配 switch return switch (shape) { case Circle c -> "圆形,半径: " + c.radius(); // 假设 Circle 有 radius() 方法 case Rectangle r -> "矩形,长: " + r.length() + ", 宽: " + r.width(); // 因为 Shape 是密封的,编译器知道只有 Circle 和 Rectangle,所以这里穷尽了所有情况。 // 如果添加了新的子类而未处理,编译器会报错。 }; } // 处理 null 值 public static String describeShapeOrNull(Shape shape) { return switch (shape) { case null -> "空对象"; case Circle c -> "圆形"; case Rectangle r -> "矩形"; }; } // 使用守卫(Guard)进行更精细的判断 public static String describeRectangle(Rectangle r) { return switch (r) { case Rectangle rect when rect.length() == rect.width() -> "正方形"; case Rectangle rect -> "普通矩形"; }; } public static void main(String[] args) { Shape circle = new Circle(5.0); Shape rectangle = new Rectangle(4.0, 6.0); Shape square = new Square(3.0); System.out.println(describeShape(circle)); // 输出: 圆形,半径: 5.0 System.out.println(describeShape(rectangle)); // 输出: 矩形,长: 4.0, 宽: 6.0 System.out.println(describeShape(square)); // 输出: 矩形,长: 3.0, 宽: 3.0 System.out.println(describeRectangle((Rectangle)square)); // 输出: 正方形 } }为什么重要:
- 消除
default:对于密封类,如果case覆盖了所有子类,可以省略default子句,编译器会保证完备性。 - 空值处理:可以直接将
null作为一个case进行处理。 - 守卫表达式:使用
when关键字在匹配类型后附加布尔条件,实现更复杂的逻辑。
3.4 新的伪随机数生成器 (PRNG) - JEP 356
是什么:引入了一套新的随机数生成器接口(RandomGenerator)和实现,提供了更灵活、更可插拔的随机数生成方案。
核心接口:RandomGenerator是所有新 PRNG 算法的统一接口。
常用实现:
L32X64MixRandom:平衡性能和质量,适用于大多数通用场景(新的默认算法)。L64X128MixRandom:更高质量,适用于科学模拟等。Xoshiro256PlusPlus、Xoroshiro128PlusPlus:非常快,适用于非加密场景(如游戏、测试)。SecureRandom:也实现了此接口,用于加密安全场景。
如何使用:
// 文件路径:src/main/java/com/example/random/NewRandomDemo.java package com.example.random; import java.util.random.*; public class NewRandomDemo { public static void main(String[] args) { // 1. 使用新的默认算法 (L32X64MixRandom) RandomGenerator rg1 = RandomGenerator.getDefault(); System.out.println("Default Algorithm: " + rg1.getClass().getSimpleName()); System.out.println("随机整数: " + rg1.nextInt(100)); // 2. 获取特定算法实例 RandomGenerator rg2 = RandomGenerator.of("L64X128MixRandom"); System.out.println("\n使用 L64X128MixRandom:"); rg2.ints(5, 1, 101).forEach(System.out::println); // 生成5个[1,100]的随机数 // 3. 使用流API生成随机数 System.out.println("\n生成随机双精度数流:"); rg1.doubles(3).forEach(System.out::println); // 4. 传统 Random 类现在也实现了 RandomGenerator 接口 java.util.Random legacyRandom = new java.util.Random(); System.out.println("\n传统Random类生成的布尔值: " + legacyRandom.nextBoolean()); } }工程建议:
- 默认升级:从
java.util.Random切换到RandomGenerator.getDefault(),通常能获得更好的性能和质量,且代码改动极小。 - 按需选择:根据应用场景(速度优先、质量优先、可重现性)选择特定的算法。
- 替换
ThreadLocalRandom:对于并发场景,可以使用RandomGenerator.getDefault().fork()或选择线程安全的算法。
3.5 其他重要特性与API增强
3.5.1 上下文特定的反序列化过滤器 (JEP 415)
允许应用程序通过ObjectInputFilter在反序列化时配置基于上下文的过滤器,以阻止恶意数据流,是增强安全性的重要特性。
// 示例:为某个特定的 ObjectInputStream 设置过滤器 ObjectInputFilter filter = ObjectInputFilter.Config.createFilter( "maxdepth=5;maxarray=1000;!com.example.secret.*" ); ObjectInputStream ois = ...; ois.setObjectInputFilter(filter);3.5.2 移除实验性的 AOT 和 JIT 编译器 (JEP 410)
移除了 GraalVM 实验性的提前编译(AOT)和即时编译(JIT)功能,因为它们未被广泛采用。GraalVM 社区版提供了独立的功能。
3.5.3 强封装 JDK 内部 API (JEP 403)
默认情况下,sun.misc.Unsafe等关键内部 API 被强封装,无法通过反射访问。这迫使开发者使用标准 API,提升了应用的安全性和可移植性。如果旧库必须使用,仍需使用--add-opens命令行参数。
3.5.4 新增 macOS 渲染管道 (JEP 382)
为 macOS 提供了基于 Apple Metal API 的新 Java 2D 渲染管道,替代了已废弃的 OpenGL 管道,提升了图形性能。
3.5.5 外部函数和内存 API (孵化器) - JEP 412
替代 JNI 的下一代 API,用于更安全、高效地调用本地库和操作堆外内存。目前仍处于孵化阶段,但代表了 Java 与原生代码交互的未来方向。
4. 综合实战案例:利用新特性重构代码
假设我们有一个简单的图形处理工具,需要计算不同图形的面积并生成报告。我们将用 JDK 17 的特性重构它。
重构前(传统写法):
// 传统多态 + instanceof abstract class OldShape { abstract double area(); } class OldCircle extends OldShape { /* ... */ } class OldRect extends OldShape { /* ... */ } public static String oldDescribe(OldShape s) { if (s instanceof OldCircle) { OldCircle c = (OldCircle) s; return “Circle with area ” + c.area(); } else if (s instanceof OldRect) { OldRect r = (OldRect) s; return “Rect with area ” + r.area(); } else { throw new IllegalArgumentException(“Unknown shape”); } }重构后(JDK 17 新特性):
// 文件路径:src/main/java/com/example/refactor/GraphicsTool.java package com.example.refactor; import com.example.sealed.*; public class GraphicsTool { // 1. 使用密封类定义图形体系,编译时确保安全 // (复用之前定义的 sealed Shape, Circle, Rectangle) // 2. 使用模式匹配 switch 进行描述,代码简洁且完备 public static String describe(Shape shape) { return switch (shape) { case Circle c -> String.format("圆形 (半径=%.2f, 面积=%.2f)", c.radius(), c.area()); case Rectangle r -> String.format("矩形 (长=%.2f, 宽=%.2f, 面积=%.2f)", r.length, r.width, r.area()); // 无需 default,因为 Shape 是密封的 }; } // 3. 使用新的 RandomGenerator 生成随机图形 public static Shape generateRandomShape(java.util.random.RandomGenerator rg) { boolean isCircle = rg.nextBoolean(); if (isCircle) { // 生成 1.0 到 10.0 之间的随机半径 double radius = 1.0 + rg.nextDouble(9.0); return new Circle(radius); } else { double length = 1.0 + rg.nextDouble(9.0); double width = 1.0 + rg.nextDouble(9.0); return new Rectangle(length, width); } } public static void main(String[] args) { // 使用新的默认随机数生成器 var random = java.util.random.RandomGenerator.getDefault(); System.out.println("生成5个随机图形报告:"); for (int i = 0; i < 5; i++) { Shape s = generateRandomShape(random); System.out.println(describe(s)); } } }重构收益:
- 安全性:密封类防止了未知子类的出现,
switch表达式在编译期就能检查是否处理了所有情况。 - 简洁性:模式匹配消除了冗余的类型转换和临时变量。
- 可读性:代码意图更清晰,逻辑更集中。
- 现代性:使用了
var局部变量类型推断和新的随机数 API。
5. 升级与迁移常见问题
从旧版本(尤其是 JDK 8 或 11)升级到 JDK 17 时,可能会遇到以下问题:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
编译错误:module X does not read module Y | 模块化(JPMS)导致的模块间访问权限问题。 | 在module-info.java中添加requires语句,或对非模块化应用,使用--add-opens/--add-reads运行时参数。 |
运行错误:访问sun.misc.*等内部 API 失败 | JDK 17 强封装了内部 API。 | 1.首选:寻找并使用标准库的替代 API(如java.util.Base64替代sun.misc.BASE64Encoder)。2.临时方案:启动时添加 JVM 参数,如 --add-opens java.base/sun.security.x509=ALL-UNNAMED。 |
| 依赖的第三方库不兼容 | 库使用了被移除或封装的 API。 | 1. 升级该库到支持 JDK 17+ 的版本。 2. 如果库已停止维护,考虑寻找替代库。 |
| 性能变化 | 默认垃圾回收器、JIT 编译器等行为可能变化。 | 进行性能基准测试,根据应用特点调整 JVM 参数(如-XX:+UseZGC启用 ZGC)。 |
模式匹配switch编译失败 | 该特性在 JDK 17 是预览特性。 | 确保编译和运行时都添加了--enable-preview标志。对于 Maven,在pom.xml中配置maven-compiler-plugin的compilerArgs。 |
Maven 配置预览特性示例:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.10.1</version> <configuration> <source>17</source> <target>17</target> <compilerArgs> <arg>--enable-preview</arg> </compilerArgs> </configuration> </plugin> <!-- 确保 surefire 插件也启用预览 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> <configuration> <argLine>--enable-preview</argLine> </configuration> </plugin> </plugins> </build>6. 最佳实践与工程建议
逐步采用,而非一次性重写:
- 在新代码中优先使用密封类、模式匹配等新特性。
- 在重构旧代码时,优先针对那些能明显提升安全性和可读性的部分(如复杂的
instanceof链)进行重构。
善用密封类进行领域建模:
- 将核心的、固定的业务概念(如订单状态:
Created,Paid,Shipped,Cancelled)定义为密封接口或类。这能使你的领域模型在编译期就更加健壮。
- 将核心的、固定的业务概念(如订单状态:
理解预览特性的风险:
- 模式匹配
switch在 JDK 17 仍是预览特性,其语法在未来的版本中可能发生不兼容的更改。在生产代码中谨慎使用,或等待其成为正式特性(已在 JDK 21 中成为正式特性)。
- 模式匹配
升级依赖和工具链:
- 确保构建工具(Maven/Gradle)、持续集成(CI)环境、IDE 都支持 JDK 17。
- 全面测试第三方依赖的兼容性,特别是那些涉及反射、字节码操作或直接使用内部 API 的库(如某些旧版本的 ASM、CGLIB 等)。
性能调优新选择:
- 评估新的
RandomGenerator实现是否能为你的应用带来性能提升。 - 如果应用对停顿时间敏感,可以考虑将垃圾回收器切换到 ZGC (
-XX:+UseZGC) 或 Shenandoah (-XX:+UseShenandoahGC),它们在 JDK 17 中已是正式特性。
- 评估新的
安全强化:
- 利用强封装内部 API 的契机,审计代码,移除对
sun.misc.*、com.sun.*等内部 API 的依赖,转向使用标准 API,这能提升应用的长期间可移植性和安全性。
- 利用强封装内部 API 的契机,审计代码,移除对
7. 总结与学习路线
通过本文的梳理,我们深入探讨了 JDK 17 作为长期支持版本带来的关键新特性:密封类让继承体系更可控,模式匹配让类型检查和代码分支更简洁安全,新的随机数API提供了更优的选择,再加上一系列的性能、安全增强和孵化器项目。
掌握这些特性,不仅能写出更现代、更健壮的 Java 代码,也是为未来版本(如 JDK 21 中成为正式特性的虚拟线程、结构化并发等)打下基础。
下一步学习建议:
- 动手实验:在本地创建一个 JDK 17 项目,将文中的示例代码敲一遍,并尝试修改和扩展。
- 升级评估:选择一个非核心的旧项目模块,尝试将其编译和运行在 JDK 17 下,解决遇到的实际兼容性问题。
- 关注正式特性:学习已在 JDK 21 中转正的特性,如虚拟线程(Virtual Threads)、记录类(Record Patterns)等,它们代表了 Java 并发和数据处理的新范式。
- 深入原理:对于感兴趣的特性,如 ZGC 的工作原理、外部函数 API 的设计,可以阅读相关的 JEP(JDK Enhancement Proposal)文档和深度技术文章。
技术升级的本质是提升效率和降低风险。JDK 17 提供了一个在稳定性和现代语言特性之间取得良好平衡的平台,值得每一位 Java 开发者投入时间学习和应用。