• 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 类,只想让 CircleSquare 继承它,不想让其他人随便加 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 优化

核心概念(通俗版)

记录类是「只读数据载体」,不用手动写 getterequals()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 新增的序列集合接口?答:SequencedCollectionSequencedSetSequencedMap;作用?答:统一有序集合的首尾操作API

四、JDK 17 vs JDK 21 核心区别(小白易懂版)

维度 JDK 17 JDK 21
定位 LTS 版本,基础增强 LTS 版本,功能更全面,性能更优
核心特性 密封类、switch 模式匹配(基础)、文本块 虚拟线程(核心)、记录类(优化)、switch 模式匹配(增强)、序列集合
适用场景 企业生产环境(稳定) 高并发场景(虚拟线程)、数据封装(记录类)、复杂匹配(switch)
底层优化 主要是语法糖和基础约束 虚拟线程的调度机制、集合API的统一化

总结

  1. 核心基础:JDK 17 和 21 都是 LTS 版本,21 在 17 基础上新增了虚拟线程等重磅特性,是目前企业的主流选择;
  2. 必学特性:小白优先掌握——JDK17 的密封类、文本块;JDK21 的虚拟线程、记录类、增强版 switch 模式匹配;
  3. 面试重点:虚拟线程与 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 classpublic 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时间。

二、核心总结(小白必记)

  1. 版本优先级:优先掌握JDK17(基础)、JDK21(主流),JDK18-20/22-25仅需了解核心特性,无需深入;
  2. 核心主线:从JDK17到25,核心演进方向是「简化开发(语法糖)+ 提升并发(虚拟线程/结构化并发)+ 性能优化(GC/API)」;
  3. 面试重点:虚拟线程(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转正
  1. 结构化并发不是JDK 21正式版,而是孵化/预览特性,JDK 23才正式转正;
  2. 记录类并非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/lastreversed()等方法,解决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模式匹配(正式增强)
数据类优化 记录类(正式版) 记录类+密封记录(增强)
并发新特性 虚拟线程(正式)+结构化并发(预览)
生产环境优先级 稳定(老项目首选) 先进(新项目首选)

总结(关键要点)

  1. 核心
    • 结构化并发不是JDK 21正式版(预览),JDK 23才转正;
    • 记录类是JDK 17正式特性,JDK 21仅增强;
  2. 特性权重排序
    • JDK 17核心:密封类 > 记录类 > switch模式匹配(预览);
    • JDK 21核心:虚拟线程 > switch模式匹配(正式) > 结构化并发(预览) > 记录类增强;
  3. 面试/开发重点
    • 面试高频:虚拟线程(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时优先用

版本选型最终建议

  1. 老项目:升级到JDK 17(LTS稳定),落地密封类+记录类,不强制升级到21;
  2. 新项目:直接用JDK 21(LTS),全量落地上述特性,IO密集场景优先用虚拟线程+结构化并发;
  3. 核心原则:特性落地优先在「核心领域层、高并发接口层」,非核心业务(比如后台管理系统)可沿用传统写法,避免过度设计。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐