JDK 17 和 JDK 21的新增特性
- JDK 17 和 JDK 21的新增特性
,覆盖核心概念、底层本质区别、入门级案例、面试高频考点,最终让小白能从概念理解到实际使用,全面掌握这些新特性。
按照「核心概念→通俗案例→底层本质→面试考点」的逻辑,分模块讲解 JDK 17 和 JDK 21 最核心、最常用的新增特性,所有内容都面向小白,用最简单的语言和案例拆解。
注:JDK 17 是长期支持版(LTS),JDK 21 也是 LTS 版本,是目前企业中最主流的两个新版本,我会重点对比两者的核心特性,同时区分「JDK 17 新增」「JDK 21 新增」「JDK 17+持续优化」三类特性。
一、先理清核心概念:LTS vs 非LTS
在讲具体特性前,先明确一个基础概念,小白必懂:
- LTS(长期支持版):如 JDK 8、17、21,Oracle 提供至少 8 年的免费支持,企业生产环境首选。
- 非LTS版:如 JDK 18-20,每 6 个月发布一个,仅提供短期支持,主要用于尝新,不适合生产。
- JDK 17 和 21 的核心区别:21 在 17 的基础上新增了更多实用特性,同时优化了 17 中部分特性的底层实现,兼容性更好、性能更高。
二、JDK 17 核心新增特性(小白必学)
1. 密封类(Sealed Classes)- JDK 17 正式版
核心概念(通俗版)
你可以把密封类理解为「限定继承的类」:普通类要么能被任意类继承,要么用 final 禁止继承;而密封类可以精确指定哪些类能继承它,其他类都不行,相当于给类的继承加了「白名单」。
底层本质
解决传统继承的「无限制扩展」问题:比如你定义了一个 Shape 类,只想让 Circle、Square 继承它,不想让其他人随便加 Triangle,密封类就能实现这个约束,底层是 JVM 层面新增了对密封类的字节码标识,编译器会校验继承关系。
入门案例(完整可运行)
// 1. 定义密封类:用 sealed 关键字,permits 指定允许继承的类
public sealed class Shape permits Circle, Square {
// 抽象方法,强制子类实现
public abstract double getArea();
}
// 2. 允许继承的子类:必须用 final/non-sealed/sealed 修饰(小白先记 final)
public final class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
// 3. 另一个允许继承的子类
public final class Square extends Shape {
private double side;
public Square(double side) {
this.side = side;
}
@Override
public double getArea() {
return side * side;
}
}
// 4. 测试类
public class SealedClassTest {
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape square = new Square(4);
System.out.println("圆面积:" + circle.getArea()); // 输出:78.53981633974483
System.out.println("正方形面积:" + square.getArea()); // 输出:16.0
}
}
面试考点
- 问:密封类的关键字是什么?答:
sealed+permits(指定子类)。 - 问:密封类的子类必须用什么修饰?答:
final(禁止再继承)、non-sealed(解除密封,允许其他类继承)、sealed(继续密封)。 - 问:密封类的作用?答:限制类的继承层级,提高代码的可维护性,避免无限制扩展导致的逻辑混乱。
2. 模式匹配 for switch(预览→JDK 17 正式)
核心概念(通俗版)
传统 switch 只能匹配「值」,JDK 17 增强后可以匹配「类型」+「值」,还能直接解构变量,不用再写大量 instanceof + 强制类型转换,代码更简洁。
底层本质
编译器会自动帮你生成 instanceof 检查和类型转换的字节码,本质是语法糖,但减少了手动写重复代码的错误,同时 switch 支持更多类型(如 String、枚举、自定义类)。
入门案例
public class SwitchPatternTest {
public static void main(String[] args) {
Object obj1 = "Hello JDK17";
Object obj2 = 21;
Object obj3 = 3.14;
printType(obj1); // 输出:字符串:Hello JDK17
printType(obj2); // 输出:整数:21
printType(obj3); // 输出:其他类型:3.14
}
public static void printType(Object obj) {
// JDK17 增强的 switch 模式匹配
switch (obj) {
// 匹配 String 类型,并将 obj 赋值给 s
case String s -> System.out.println("字符串:" + s);
// 匹配 Integer 类型,并将 obj 赋值给 i
case Integer i -> System.out.println("整数:" + i);
// 默认情况
default -> System.out.println("其他类型:" + obj);
}
}
}
面试考点
- 问:JDK 17 中 switch 模式匹配解决了什么问题?答:简化
instanceof+ 类型转换的代码,减少空指针和类型转换错误。 - 问:switch 模式匹配的语法特点?答:支持类型匹配、变量解构,箭头语法
->替代传统break,更简洁。
3. JDK 17 其他实用特性(小白入门级)
| 特性 | 核心概念(通俗) | 入门案例 | 面试考点 |
|---|---|---|---|
| 文本块(Text Blocks) | 用 """ 包裹多行字符串,不用写 \n \t,适合写JSON、SQL |
String sql = """ SELECT * FROM user WHERE age > 18 """; |
问:文本块的分隔符?答:""";问:优势?答:避免转义字符,提高多行字符串可读性 |
| 简化空指针判断(NullPointerException 增强) | JVM 会在异常信息中显示具体哪个变量为空,不用再猜 | String name = null; System.out.println(name.length()); 异常会显示:Cannot invoke "String.length()" because "name" is null |
问:JDK 17 对 NPE 的优化?答:精准提示空变量,简化调试 |
三、JDK 21 核心新增特性(小白必学)
JDK 21 是 17 的升级版,新增特性更贴近实际开发,其中虚拟线程是重中之重。
1. 虚拟线程(Virtual Threads)- JDK 21 正式版
核心概念(通俗版)
传统线程(OS线程)是「重量级」的,创建1000个就可能卡爆;虚拟线程是JVM管理的「轻量级线程」,创建100万个都没问题,而且切换成本极低,专门解决高并发场景下的线程资源不足问题。
你可以把 OS 线程比作「火车车厢」,虚拟线程比作「车厢里的乘客」:1 个 OS 线程可以承载上千个虚拟线程,JVM 负责调度乘客,不用麻烦操作系统,效率大幅提升。
底层本质
- OS 线程:由操作系统内核调度,创建/切换成本高,数量上限低(一般几千个)。
- 虚拟线程:由 JVM 的
ForkJoinPool调度,不占用 OS 线程的内核资源,创建/切换成本几乎可以忽略,数量上限可达百万级。 - 虚拟线程不是替换 OS 线程,而是「挂载」在 OS 线程上运行,当虚拟线程遇到 IO 阻塞(如读写文件、调用接口)时,会自动让出 OS 线程,让其他虚拟线程运行,IO 完成后再重新挂载。
入门案例(对比传统线程 vs 虚拟线程)
import java.util.concurrent.Executors;
public class VirtualThreadTest {
public static void main(String[] args) throws InterruptedException {
// 需求:创建10000个线程,每个线程休眠1秒(模拟IO阻塞)
// 1. 传统线程池(OS线程):创建10000个会卡顿甚至报错
long start1 = System.currentTimeMillis();
try (var executor = Executors.newFixedThreadPool(100)) {
for (int i = 0; i < 10000; i++) {
executor.submit(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
return null;
});
}
}
long end1 = System.currentTimeMillis();
System.out.println("传统线程耗时:" + (end1 - start1) + "ms"); // 约10000ms+
// 2. 虚拟线程(JVM线程):创建10000个轻松运行
long start2 = System.currentTimeMillis();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10000; i++) {
executor.submit(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
return null;
});
}
}
long end2 = System.currentTimeMillis();
System.out.println("虚拟线程耗时:" + (end2 - start2) + "ms"); // 约1000ms左右
}
}
面试考点(高频!)
- 问:虚拟线程和 OS 线程的区别?答:① 调度者不同(JVM vs 操作系统);② 成本不同(轻量 vs 重量);③ 数量上限不同(百万级 vs 千级);④ 阻塞处理不同(自动让出 OS 线程 vs 占用 OS 线程)。
- 问:虚拟线程的使用场景?答:高并发 IO 场景(如接口调用、数据库操作、文件读写),不适合 CPU 密集型场景(CPU 密集型用传统线程池更合适)。
- 问:如何创建虚拟线程?答:
Executors.newVirtualThreadPerTaskExecutor()或Thread.startVirtualThread(Runnable)。
2. 记录类(Records)- JDK 16 预览→JDK 21 优化
核心概念(通俗版)
记录类是「只读数据载体」,不用手动写 getter、equals()、hashCode()、toString(),JVM 自动生成,适合用来封装纯数据(如 DTO、VO)。
底层本质
编译器自动为记录类生成:
- 全参构造器;
- 每个字段的
getter(字段名就是方法名,如name()而非getName()); - 重写
equals()、hashCode()(基于所有字段); - 重写
toString()(格式:类名(字段1=值1, 字段2=值2))。
入门案例
// 定义记录类:用 record 关键字,括号内是字段
public record User(String name, int age, String email) {}
// 测试类
public class RecordTest {
public static void main(String[] args) {
User user = new User("张三", 25, "zhangsan@test.com");
// 自动生成的 getter(直接用字段名)
System.out.println(user.name()); // 输出:张三
System.out.println(user.age()); // 输出:25
// 自动生成的 toString()
System.out.println(user); // 输出:User[name=张三, age=25, email=zhangsan@test.com]
// 自动生成的 equals()
User user2 = new User("张三", 25, "zhangsan@test.com");
System.out.println(user.equals(user2)); // 输出:true
}
}
面试考点
- 问:记录类的关键字?答:
record。 - 问:记录类的特点?答:① 不可变(字段默认
final);② 自动生成构造器、getter、equals、hashCode、toString;③ 不能继承其他类(但可以实现接口)。 - 问:记录类和普通类的区别?答:普通类需要手动写数据相关方法,记录类自动生成,专注于数据载体。
3. JDK 21 其他实用特性
| 特性 | 核心概念(通俗) | 入门案例 | 面试考点 |
|---|---|---|---|
| switch 模式匹配(增强) | 支持「嵌套模式」「常量模式」,能匹配更复杂的场景 | case Point(int x, int y) when x > y -> System.out.println("x大于y"); |
问:JDK21 对 switch 的增强?答:支持嵌套模式、when 条件判断,匹配逻辑更灵活 |
| 序列集合(Sequenced Collections) | 新增 SequencedCollection/SequencedMap 接口,统一处理「有序集合」的首尾操作,不用再区分 List/Deque |
list.addFirst(1); map.putLast("key", "value"); |
问:JDK21 新增的序列集合接口?答:SequencedCollection、SequencedSet、SequencedMap;作用?答:统一有序集合的首尾操作API |
四、JDK 17 vs JDK 21 核心区别(小白易懂版)
| 维度 | JDK 17 | JDK 21 |
|---|---|---|
| 定位 | LTS 版本,基础增强 | LTS 版本,功能更全面,性能更优 |
| 核心特性 | 密封类、switch 模式匹配(基础)、文本块 | 虚拟线程(核心)、记录类(优化)、switch 模式匹配(增强)、序列集合 |
| 适用场景 | 企业生产环境(稳定) | 高并发场景(虚拟线程)、数据封装(记录类)、复杂匹配(switch) |
| 底层优化 | 主要是语法糖和基础约束 | 虚拟线程的调度机制、集合API的统一化 |
总结
- 核心基础:JDK 17 和 21 都是 LTS 版本,21 在 17 基础上新增了虚拟线程等重磅特性,是目前企业的主流选择;
- 必学特性:小白优先掌握——JDK17 的密封类、文本块;JDK21 的虚拟线程、记录类、增强版 switch 模式匹配;
- 面试重点:虚拟线程与 OS 线程的区别、记录类的特点、密封类的作用是高频考点,核心要理解「语法糖背后的底层逻辑」和「实际使用场景」。
你想系统了解从JDK 17到JDK 25的核心新增特性,我会按版本梳理每个版本的关键特性、核心用途、适用场景,重点区分LTS版本(长期支持)和非LTS版本(短期迭代),用小白能懂的语言讲清每个特性的价值,同时标注面试/开发中的高频考点。
先明确版本基础规则(必知)
- LTS版本:JDK 17、21是LTS(长期支持,企业生产首选),JDK 25非LTS(短期迭代,仅尝新用);
- 发布节奏:每6个月发布一个版本(17→18→19→20→21→22→23→24→25);
- 特性演进:非LTS版本的预览特性,成熟后会在后续LTS版本中正式落地。
历史变更
一、核心版本梳理(JDK17→JDK25)
1. JDK 17(2021.09,LTS)—— 基础增强版
作为经典LTS版本,核心特性已在之前讲解,这里仅回顾核心:
| 核心特性 | 用途 | 高频考点 |
|---|---|---|
| 密封类(Sealed Classes) | 限制类的继承范围 | 关键字sealed/permits、子类修饰规则 |
| 模式匹配for switch | 简化类型判断+类型转换 | 语法糖底层逻辑、空指针优化 |
| 文本块(Text Blocks) | 简化多行字符串编写(JSON/SQL) | 分隔符"""、转义字符优化 |
| NPE精准提示 | 异常信息显示具体空变量 | 调试效率提升的底层逻辑 |
2. JDK 18(2022.03,非LTS)—— 小特性迭代
仅包含少量预览/孵化特性,无核心重磅更新:
- UTF-8默认字符集:统一所有平台的默认字符集为UTF-8,解决中文乱码、跨平台字符集不一致问题;
- 简单Web服务器(jwebserver):内置轻量HTTP服务器(仅用于测试),命令行
jwebserver即可启动,无需Tomcat; - 代码片段API(预览):简化代码片段的解析/编译,主要用于IDE工具开发,普通开发几乎不用。
3. JDK 19(2022.09,非LTS)—— 虚拟线程首秀
核心是虚拟线程的首次预览,其他特性均为补充:
| 核心特性 | 用途 | 高频考点 |
|---|---|---|
| 虚拟线程(预览) | 轻量级线程,解决高并发IO场景线程瓶颈 | 与OS线程的区别、调度机制 |
| 结构化并发(孵化) | 简化多线程任务编排,避免线程泄漏 | 核心类StructuredTaskScope |
| 外部函数与内存API(预览) | 安全调用本地代码(替代JNI) | 内存安全、性能优化 |
4. JDK 20(2023.03,非LTS)—— 特性迭代
无新特性,仅对JDK19的预览特性(虚拟线程、结构化并发)进行优化,核心变化:
- 虚拟线程支持更多场景(如同步块、ThreadLocal);
- 外部函数API增强内存安全校验。
5. JDK 21(2023.09,LTS)—— 重磅升级LTS
目前企业主流版本,核心特性如下:
| 核心特性 | 用途 | 高频考点 |
|---|---|---|
| 虚拟线程(正式版) | 百万级轻量级线程,高并发IO首选 | 创建方式、适用场景(IO密集型) |
| 记录类(优化) | 简化数据载体(DTO/VO),支持密封记录 | record关键字、不可变性 |
| switch模式匹配(增强) | 嵌套模式、when条件判断 | 复杂类型匹配、空值处理 |
| 序列集合(Sequenced Collections) | 统一有序集合API(addFirst/last) | SequencedCollection/Map接口 |
| 未命名模式/变量(预览) | 简化无意义变量命名(如_替代变量名) |
语法糖、代码简洁性 |
6. JDK 22(2024.03,非LTS)—— 语法糖增强
核心是简化开发的语法糖,无底层架构变化:
- 字符串模板(预览):替代
String.format,支持动态拼接(如STR."Hello \{name}"); - 未命名类和主方法(预览):简化入门代码,无需写
public class和public static void main;// JDK22简化写法(无需类和主方法) void main() { System.out.println("Hello JDK22"); } - 作用域值(预览):替代ThreadLocal,更轻量的线程内数据传递。
7. JDK 23(2024.09,非LTS)—— 并发/语法优化
- 结构化并发(正式版):解决多线程任务的生命周期管理,避免线程泄漏;
// 结构化并发示例:子任务异常时,父任务自动取消其他子任务 try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Future<String> f1 = scope.fork(() -> fetchUser()); Future<Integer> f2 = scope.fork(() -> fetchAge()); scope.join(); // 等待所有子任务 scope.throwIfFailed(); // 有异常则抛出 System.out.println(f1.result() + ":" + f2.result()); } - 字符串模板(第二次预览):优化语法,支持更多场景(如SQL拼接防注入);
- 虚拟线程优化:支持线程组、优先级设置,更贴近OS线程特性。
8. JDK 24(2025.03,非LTS)—— 工具/性能优化
- JDK飞行记录器(JFR)增强:更轻量的性能监控,支持实时分析虚拟线程;
- 外部函数API(正式版):安全调用C/C++代码,性能优于JNI;
- 语法优化:未命名变量支持更多场景(如try-catch的异常变量
catch (Exception _))。
9. JDK 25(2025.09,非LTS)—— 最新迭代(截至2026年的规划)
目前为规划版本,核心特性聚焦「简化开发+性能提升」:
- 字符串模板(正式版):成为标配语法,替代所有字符串拼接场景;
- 作用域值(正式版):全面替代ThreadLocal,降低内存泄漏风险;
- 虚拟线程深度优化:支持CPU密集型场景,提升调度效率;
- 垃圾回收器优化:G1/ZGC支持虚拟线程的内存管理,降低STW时间。
二、核心总结(小白必记)
- 版本优先级:优先掌握JDK17(基础)、JDK21(主流),JDK18-20/22-25仅需了解核心特性,无需深入;
- 核心主线:从JDK17到25,核心演进方向是「简化开发(语法糖)+ 提升并发(虚拟线程/结构化并发)+ 性能优化(GC/API)」;
- 面试重点:虚拟线程(JDK21正式)、密封类(JDK17)、switch模式匹配(JDK17→21增强)是高频考点,其他特性仅作了解即可。
精准梳理JDK 17/21的核心特性边界:
一、核心结论(总结)
| 特性 | JDK 17状态 | JDK 21状态 | JDK 23状态 | 核心定位 |
|---|---|---|---|---|
| 密封类(Sealed Classes) | 正式版(核心特性) | 完全兼容(无变化) | 无变化 | JDK 17「招牌特性」之一 |
| 虚拟线程 | 预览版(JDK 19/20预览) | 正式版(核心特性) | 优化(支持线程组/优先级) | JDK 21「头号特性」 |
| switch模式匹配 | 预览版(基础语法) | 正式版(增强语法) | 无变化 | JDK 21核心语法糖 |
| 记录类(Record) | 正式版(JDK 16转正) | 增强(支持密封记录) | 无变化 | JDK 17已成熟,JDK 21小幅增强 |
| 结构化并发(StructuredTaskScope) | 未引入 | 预览版(孵化) | 正式版 | JDK 21预览、JDK 23转正 |
- 结构化并发不是JDK 21正式版,而是孵化/预览特性,JDK 23才正式转正;
- 记录类并非JDK 21新增,JDK 16已预览、JDK 17正式转正,JDK 21仅做小幅增强。
二、逐个拆解:核心特性的版本&价值(精准版)
1. JDK 17的核心特性(LTS,2021)—— 「语法/安全增强」
JDK 17作为LTS版本,核心是「稳定+实用」,没有颠覆性架构变化,核心特性:
- 密封类(正式版): 核心是
sealed/permits关键字限制类的继承范围,解决「继承失控」问题(比如Shape类只允许Circle/Square继承),是JDK
17最具标志性的特性,也是面试高频考点;- 记录类(正式版): JDK 16预览、JDK 17转正,核心是简化「数据载体类」(DTO/VO),自动生成equals/hashCode/toString,替代传统POJO的样板代码;
- switch模式匹配(预览版):
仅支持基础类型匹配(如case Circle c:),无when条件,需开启预览参数才能用; - 其他实用特性:
UTF-8默认字符集、NPE精准提示、移除废弃的Applet API(安全优化),这些是「小而实用」的增强,无架构级变化。
2. JDK 21的核心特性(LTS,2023)—— 「并发+语法大升级」
JDK 21是近5年最重磅的LTS版本,核心是「颠覆性并发特性+语法糖成熟化」:
- 虚拟线程(正式版): 这是JDK 21的「头号核心」,百万级轻量级线程彻底解决传统OS线程的高并发瓶颈(IO密集型场景性能提升10~100倍),是JDK
21最具里程碑意义的特性;- switch模式匹配(正式版): 从JDK 17的基础预览升级为「完整正式版」,支持
when条件、嵌套模式、密封类省略default,成为生产环境可用的核心语法糖;- 记录类(增强): 支持「密封记录」(
sealed record User permits AdminUser {}),把密封类和记录类结合,进一步简化数据类的继承约束;
- 结构化并发(预览/孵化):
注意!JDK 21中StructuredTaskScope是「孵化特性」(需加--enable-preview+模块导出才能用),并非正式版,核心是为了和虚拟线程配套,解决多线程生命周期管理问题; - 序列集合(Sequenced Collections):
新增SequencedCollection/Map接口,统一有序集合的addFirst/last、reversed()等方法,解决List/Deque/LinkedHashMap的API碎片化问题。
3. JDK 23的结构化并发(正式版)—— 「并发特性补全」
JDK 23作为非LTS版本,核心是把JDK 21预览的结构化并发「转正」:
- 移除预览参数,直接可用于生产环境;
- 优化
StructuredTaskScope的异常处理、子任务取消逻辑,适配虚拟线程的调度特性; - 无其他颠覆性特性,核心是「补全JDK 21的并发生态」。
三、JDK 17 vs JDK 21核心特性对比(通俗版)
| 维度 | JDK 17(2021 LTS) | JDK 21(2023 LTS) |
|---|---|---|
| 核心方向 | 语法糖成熟化+安全优化 | 并发架构升级+语法糖增强 |
| 标志性特性 | 密封类(控制继承) | 虚拟线程(重构并发) |
| 语法糖 | switch模式匹配(基础预览) | switch模式匹配(正式增强) |
| 数据类优化 | 记录类(正式版) | 记录类+密封记录(增强) |
| 并发新特性 | 无 | 虚拟线程(正式)+结构化并发(预览) |
| 生产环境优先级 | 稳定(老项目首选) | 先进(新项目首选) |
总结(关键要点)
- 核心:
- 结构化并发不是JDK 21正式版(预览),JDK 23才转正;
- 记录类是JDK 17正式特性,JDK 21仅增强;
- 特性权重排序:
- JDK 17核心:密封类 > 记录类 > switch模式匹配(预览);
- JDK 21核心:虚拟线程 > switch模式匹配(正式) > 结构化并发(预览) > 记录类增强;
- 面试/开发重点:
- 面试高频:虚拟线程(JDK 21)、密封类(JDK 17)、switch模式匹配(JDK 21增强);
- 开发实用:虚拟线程(高并发)、记录类(简化DTO)、结构化并发(JDK 23+,简化多线程)。
简单来说:JDK 17是「稳扎稳打的LTS」,JDK 21是「重构并发的革命性LTS」,结构化并发是JDK 21埋下的「并发生态补全伏笔」,JDK 23完成转正。
结合JDK 17/21核心特性和实际业务场景,给出「特性选型+最佳实践」——核心是讲清每个关键特性「在什么业务场景下必须用/优先用」,并落地到具体的业务问题解决,按「特性→核心价值→典型业务场景→最佳实践代码」的逻辑拆解,覆盖领域驱动、IO密集、继承限制等核心场景。
先明确核心前提
| JDK版本 | 核心定位 | 选型原则 |
|---|---|---|
| JDK 17 | 稳定LTS、语法糖成熟 | 老项目升级/追求稳定性、需密封类/记录类场景 |
| JDK 21 | 革命性LTS、并发能力升级 | 新项目/高并发IO场景、需虚拟线程/结构化并发 |
一、JDK 17核心特性:密封类(Sealed Classes)
1. 核心价值
限制类的继承范围,解决「继承失控」问题——确保核心业务类只能被指定子类继承,避免非预期的子类篡改业务逻辑。
2. 必须使用的业务场景
当你的业务类是「有限固定子类的抽象模板」,且不允许外部随意扩展时(比如领域模型中的「有限状态/类型」),必须用密封类,典型场景:
场景1:领域驱动(DDD)中的「限界上下文固定类型」
业务问题:电商订单的「支付状态」只允许「待支付、已支付、已退款、已取消」4种类型,不允许开发人员随意新增「部分支付、超时支付」等未定义状态,避免状态逻辑混乱。
最佳实践:用密封类定义订单状态的根类型,仅允许指定子类继承。
// 密封类:仅允许4个子类继承,杜绝非预期状态
public abstract sealed class OrderStatus
permits PendingPayment, Paid, Refunded, Cancelled {
// 状态通用逻辑:获取状态描述
public abstract String getDesc();
// 状态通用逻辑:判断是否可操作(比如待支付可取消,已支付可退款)
public abstract boolean canOperate(OrderOperate operate);
}
// 固定子类:待支付
public final class PendingPayment extends OrderStatus {
@Override
public String getDesc() { return "待支付"; }
@Override
public boolean canOperate(OrderOperate operate) {
// 待支付仅允许「取消、支付」操作
return operate == OrderOperate.CANCEL || operate == OrderOperate.PAY;
}
}
// 固定子类:已支付
public final class Paid extends OrderStatus {
@Override
public String getDesc() { return "已支付"; }
@Override
public boolean canOperate(OrderOperate operate) {
// 已支付仅允许「退款」操作
return operate == OrderOperate.REFUND;
}
}
// 其他子类(Refunded/Cancelled)同理
public enum OrderOperate { PAY, CANCEL, REFUND }
选型价值:
- 强制约束:新入职开发人员无法随意创建
PartialPaid(部分支付)子类,避免状态逻辑失控; - 编译期校验:新增状态必须修改密封类的
permits列表,便于代码评审和版本管控; - 逻辑安全:
canOperate方法的逻辑仅需覆盖4种固定状态,无需处理未知状态的异常分支。
场景2:框架/中间件中的「固定扩展点」
业务问题:自研RPC框架的「序列化方式」仅支持JSON、Protobuf、Hessian3种,不允许业务方自定义序列化方式(避免序列化协议不兼容)。
最佳实践:密封类定义序列化器根接口,仅允许指定实现类。
public abstract sealed class RpcSerializer
permits JsonSerializer, ProtobufSerializer, HessianSerializer {
public abstract byte[] serialize(Object obj);
public abstract <T> T deserialize(byte[] data, Class<T> clazz);
}
// 固定实现类,禁止外部扩展
public final class JsonSerializer extends RpcSerializer { /* 实现逻辑 */ }
3. 选型注意事项
- 密封类的子类必须用
final/sealed/non-sealed修饰,生产环境优先用final(杜绝二次扩展); - 仅当「子类数量固定、需严格限制继承」时使用,普通业务类(如User、Product)无需使用;
- JDK 17+才能用,老项目升级到17后,优先在「核心领域模型」中落地。
二、JDK 17核心特性:记录类(Record)
1. 核心价值
自动生成「不可变数据载体」的样板代码(equals/hashCode/toString/getter),简化DTO/VO/值对象(Value Object)的编写,且天然支持不可变性。
2. 优先使用的业务场景
场景:领域驱动(DDD)中的「值对象(VO)」
业务问题:电商订单的「收货地址」是值对象(无唯一ID,属性全相等即视为同一地址),传统POJO需要手写20+行样板代码,且易因忘记重写equals导致逻辑错误。
最佳实践:用记录类定义值对象,自动实现不可变性和核心方法。
// 记录类:收货地址值对象(不可变,自动生成equals/hashCode/toString/getter)
public record ReceiptAddress(
String province,
String city,
String detail,
String phone,
String receiver
) {
// 可选:自定义校验逻辑(比如手机号格式)
public ReceiptAddress {
if (!phone.matches("^1[3-9]\\d{9}$")) {
throw new IllegalArgumentException("手机号格式错误:" + phone);
}
}
}
// 业务使用:值对象比较
public class OrderService {
public boolean isSameAddress(ReceiptAddress a1, ReceiptAddress a2) {
// 自动生成的equals方法,直接比较所有属性
return a1.equals(a2);
}
}
选型价值:
- 代码极简:替代传统20+行的POJO,减少80%样板代码;
- 不可变性:记录类的属性默认
final,避免业务逻辑中意外修改值对象(比如地址被篡改); - 逻辑安全:值对象的相等性判断依赖所有属性,符合DDD值对象的设计原则。
3. 选型注意事项
- 仅用于「数据载体」(DTO/VO/值对象),不用于「有业务行为的实体类」(比如Order实体有
pay()方法,不适合用record); - 记录类不可继承(默认final),如需扩展可结合密封类(JDK 21增强);
- JDK 16+预览,JDK 17正式支持,DDD项目优先在值对象层落地。
三、JDK 21核心特性:虚拟线程(Virtual Threads)
1. 核心价值
百万级轻量级线程,解决IO密集型场景下「线程数不足、上下文切换开销大」的问题,大幅提升并发能力。
2. 必须使用的业务场景
场景1:高并发IO密集型接口(微服务调用/数据库查询)
业务问题:电商秒杀接口需要同时调用「库存服务、用户服务、优惠券服务」3个下游接口(每个接口平均耗时50ms,IO等待占99%),传统OS线程池(核心线程数200)只能支撑4000 QPS,无法满足秒杀10万QPS的需求。
最佳实践:用虚拟线程替代OS线程,支撑百万级并发。
// JDK 21虚拟线程实现:秒杀接口
@RestController
@RequestMapping("/seckill")
public class SeckillController {
@Autowired
private InventoryService inventoryService;
@Autowired
private UserService userService;
@Autowired
private CouponService couponService;
@PostMapping("/submit")
public Result submitSeckill(Long userId, Long goodsId) {
// 虚拟线程执行:无需手动创建线程池,JDK自动管理
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 并行调用3个下游接口(虚拟线程执行,百万级并发无压力)
Future<Boolean> inventoryFuture = scope.fork(() -> inventoryService.checkStock(goodsId));
Future<User> userFuture = scope.fork(() -> userService.getUserInfo(userId));
Future<Coupon> couponFuture = scope.fork(() -> couponService.getAvailableCoupon(userId, goodsId));
// 等待所有调用完成,任一失败则终止
scope.join();
scope.throwIfFailed();
// 处理结果
boolean hasStock = inventoryFuture.result();
User user = userFuture.result();
Coupon coupon = couponFuture.result();
if (hasStock && user != null) {
return Result.success("秒杀成功", coupon);
} else {
return Result.fail("秒杀失败:库存不足或用户不存在");
}
} catch (Exception e) {
return Result.fail("秒杀失败:" + e.getMessage());
}
}
}
选型价值:
- 并发能力提升100倍:虚拟线程无需OS内核调度,50ms IO等待期间,虚拟线程会被挂起,CPU可处理其他虚拟线程,1个OS线程可承载1000+虚拟线程;
- 代码极简:无需手动配置线程池参数(核心线程数、队列大小),避免线程池参数调优的复杂度;
- 无资源泄漏:结合结构化并发,接口超时/异常时自动终止所有下游调用的虚拟线程。
场景2:批量IO任务(数据导出/日志采集)
业务问题:需要批量采集10万个用户的行为日志(每个日志采集需调用远程接口,耗时10ms),传统线程池需配置大量核心线程(易导致线程数过多),虚拟线程可轻松处理。
最佳实践:
public class LogCollectService {
public void collectUserLogs(List<Long> userIds) {
// 虚拟线程批量执行
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
userIds.forEach(userId -> executor.submit(() -> {
// 调用远程接口采集日志(IO密集)
LogDTO log = remoteLogService.collect(userId);
logRepository.save(log);
}));
} // try-with-resources自动关闭线程池,终止所有未完成的虚拟线程
}
}
3. 选型注意事项
- 仅用于IO密集型场景:CPU密集型场景(比如大数据计算)用虚拟线程无优势(CPU一直忙碌,无法挂起);
- JDK 21+正式支持:JDK 19/20为预览版,生产环境需升级到21;
- 避免同步阻塞:虚拟线程中避免长时间同步阻塞(比如
synchronized锁持有100ms),会导致载体OS线程被占用,降低并发能力。
四、JDK 21核心特性:结构化并发(StructuredTaskScope)
1. 核心价值
结构化管理多线程任务生命周期,解决「线程泄漏、异常处理繁琐、任务编排复杂」问题。
2. 优先使用的业务场景
场景:多数据源竞速查询(提升接口响应速度)
业务问题:用户信息同时存储在「主库、备库1、备库2」,需要取最快返回的结果(备库可能比主库快),传统方式需手写竞速逻辑,易出错。
最佳实践:用ShutdownOnSuccess实现多数据源竞速。
public class UserService {
@Autowired
private UserRepository mainDbRepo;
@Autowired
private UserRepository slave1Repo;
@Autowired
private UserRepository slave2Repo;
public User getUserById(Long userId) {
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<User>()) {
// 3个数据源并行查询(虚拟线程执行)
scope.fork(() -> mainDbRepo.getById(userId));
scope.fork(() -> slave1Repo.getById(userId));
scope.fork(() -> slave2Repo.getById(userId));
// 等待第一个成功的结果,自动取消其他查询
scope.join();
return scope.result(); // 返回最快的结果
} catch (Exception e) {
throw new RuntimeException("查询用户失败", e);
}
}
}
选型价值:
- 响应速度提升:取最快的数据源结果,接口耗时从50ms降至10ms;
- 资源节约:第一个结果返回后,自动取消其他两个查询,避免无效的数据库连接占用;
- 代码极简:替代传统20+行的竞速逻辑,降低维护成本。
五、JDK 21核心特性:Switch模式匹配(正式版)
1. 核心价值
简化复杂类型判断+条件过滤,替代繁琐的if-else/instanceof。
2. 优先使用的业务场景
场景:DDD中的「多态逻辑处理」
业务问题:电商订单的「不同状态处理不同逻辑」(待支付扣库存、已支付生成物流单、已退款恢复库存),传统if-else+instanceof代码冗余。
最佳实践:用switch模式匹配+密封类,简化状态逻辑处理。
public class OrderProcessService {
public void processOrder(Order order) {
OrderStatus status = order.getStatus();
// JDK 21 switch模式匹配:类型+条件过滤
switch (status) {
case PendingPayment p when p.canOperate(OrderOperate.PAY):
// 待支付且可支付:扣库存
inventoryService.deduct(order.getGoodsId(), order.getCount());
break;
case Paid p when p.canOperate(OrderOperate.REFUND):
// 已支付且可退款:恢复库存
inventoryService.restore(order.getGoodsId(), order.getCount());
break;
case Cancelled c:
// 已取消:释放优惠券
couponService.release(order.getCouponId());
break;
// 密封类覆盖所有子类,无需default
}
}
}
选型价值:
- 代码简洁:替代传统多层
if (status instanceof PendingPayment),减少50%代码; - 编译期校验:密封类新增子类时,switch会提示「缺少case分支」,避免逻辑遗漏;
- 条件聚合:
when条件直接写在case中,无需单独写if判断。
六、整体选型总结(核心特性+业务场景+选型原则)
| 特性 | JDK版本 | 核心业务场景 | 选型原则 |
|---|---|---|---|
| 密封类 | 17+ | DDD固定类型、框架固定扩展点、有限状态模型 | 子类数量固定、需严格限制继承时必须用 |
| 记录类 | 17+ | DDD值对象、DTO/VO、不可变数据载体 | 仅数据无业务行为时优先用 |
| 虚拟线程 | 21+ | 高并发IO接口、批量IO任务、微服务多下游调用 | IO密集型场景必须用,CPU密集型不用 |
| 结构化并发 | 21+(预览)/23+(正式) | 多任务依赖/竞速、批量任务、接口超时控制 | 多线程任务需生命周期管理时优先用 |
| Switch模式匹配 | 21+ | DDD多态逻辑、复杂类型判断、状态机处理 | 替代多层if-else/instanceof时优先用 |
版本选型最终建议
- 老项目:升级到JDK 17(LTS稳定),落地密封类+记录类,不强制升级到21;
- 新项目:直接用JDK 21(LTS),全量落地上述特性,IO密集场景优先用虚拟线程+结构化并发;
- 核心原则:特性落地优先在「核心领域层、高并发接口层」,非核心业务(比如后台管理系统)可沿用传统写法,避免过度设计。
更多推荐




所有评论(0)