跳到主要内容 Java JDK21 新特性详解 | 极客日志
Java java
Java JDK21 新特性详解 JDK21 作为长期支持版引入多项核心特性。虚拟线程提供轻量级高并发能力,降低内存占用与切换开销;模式匹配 for switch 简化分支判断与类型检查;记录模式支持数据解构;密封类控制继承范围;分代 ZGC 优化垃圾回收性能;序列化集合标准化顺序操作。预览特性包括字符串模板简化拼接、结构化并发管理任务生命周期、向量 API 提升数值计算效率。这些特性显著提升开发效率与程序性能,适用于高并发及大数据场景。
laoliangsh 发布于 2026/3/24 0 浏览Java JDK21 新特性详解
JDK21 是 Oracle 发布的继 JDK8、JDK11、JDK17 之后的第四个长期支持版(LTS),于 2023 年 9 月 19 日正式发布,共包含 15 个新特性,涵盖语言增强、性能优化、API 扩展等多个维度,极大提升了开发效率、程序性能和代码安全性。
一、核心正式特性
1. 虚拟线程(Virtual Threads,JEP 444)—— 轻量级高并发利器
1.1 特性说明
虚拟线程是 JDK21 最受关注的核心特性,它是由 JVM 管理的轻量级线程,而非操作系统内核线程,彻底解决了传统平台线程(Platform Thread)资源占用高、上下文切换开销大的问题。虚拟线程的内存占用极低(每个仅需几 KB),可轻松创建数百万个并发任务,尤其适合 I/O 密集型场景(如接口调用、数据库操作、文件读写等),能极大提升程序吞吐量,且无需修改现有线程相关代码,上手成本极低。
核心优势:
轻量级:内存占用远低于平台线程,单个 JVM 可创建数百万个虚拟线程。
低开销:上下文切换由 JVM 管理,无需操作系统内核参与,开销极小。
易迁移:现有基于 Runnable、Callable 的代码可直接迁移,无需修改业务逻辑。
结构化并发:配合 Executors.newVirtualThreadPerTaskExecutor(),简化并发任务管理。
调度优化:虚拟线程阻塞时会自动切换到其他正在运行的线程,充分利用 CPU 时间,避免资源闲置。
无规模限制:不受操作系统对线程数量的限制,理论上支持无限量创建(受限于内存)。
1.2 代码样例(4 种创建方式,推荐第三种) import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
public class VirtualThreadsDemo {
public static void main (String[] args) throws InterruptedException {
Thread.startVirtualThread(() -> {
System.out.println("方式 1:虚拟线程运行中,线程类型:" + Thread.currentThread().getClass().getSimpleName());
try {
TimeUnit.MILLISECONDS.sleep(100 );
} catch (InterruptedException e) {
throw new RuntimeException (e);
}
System.out.println("方式 1:虚拟线程执行完毕" );
});
Thread virtualThread = Thread.ofVirtual()
.name("virtual-thread-2" )
.unstarted(() -> {
System.out.println("方式 2:虚拟线程运行中,线程名称:" + Thread.currentThread().getName());
try {
TimeUnit.MILLISECONDS.sleep(150 );
} catch (InterruptedException e) {
throw new RuntimeException (e);
}
System.out.println("方式 2:虚拟线程执行完毕" );
});
virtualThread.start();
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0 ; i < 5 ; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("方式 3:虚拟线程任务" + taskId + "运行中,线程 ID:" + Thread.currentThread().threadId());
try {
TimeUnit.MILLISECONDS.sleep(200 );
} catch (InterruptedException e) {
throw new RuntimeException (e);
}
System.out.println("方式 3:虚拟线程任务" + taskId + "执行完毕" );
});
}
}
ThreadFactory factory = Thread.ofVirtual()
.name("custom-virtual-thread-" , 0 )
.factory();
Thread customThread = factory.newThread(() -> {
System.out.println("方式 4:自定义虚拟线程运行中,线程名称:" + Thread.currentThread().getName());
try {
TimeUnit.MILLISECONDS.sleep(250 );
} catch (InterruptedException e) {
throw new RuntimeException (e);
}
System.out.println("方式 4:自定义虚拟线程执行完毕" );
});
customThread.start();
TimeUnit.SECONDS.sleep(1 );
System.out.println("所有虚拟线程执行完毕,主线程退出" );
}
}
1.3 运行结果(关键信息) 方式 1 :虚拟线程运行中,线程类型:VirtualThread
方式 2 :虚拟线程运行中,线程名称:virtual -thread-2
方式 3 :虚拟线程任务 3 运行中,线程 ID:40
方式 3 :虚拟线程任务 2 运行中,线程 ID:39
方式 3 :虚拟线程任务 4 运行中,线程 ID:41
方式 3 :虚拟线程任务 0 运行中,线程 ID:37
方式 3 :虚拟线程任务 1 运行中,线程 ID:38
方式 1 :虚拟线程执行完毕
方式 2 :虚拟线程执行完毕
方式 3 :虚拟线程任务 0 执行完毕
方式 3 :虚拟线程任务 2 执行完毕
方式 3 :虚拟线程任务 4 执行完毕
方式 3 :虚拟线程任务 3 执行完毕
方式 3 :虚拟线程任务 1 执行完毕
方式 4 :自定义虚拟线程运行中,线程名称:custom-virtual -thread-0
方式 4 :自定义虚拟线程执行完毕
所有虚拟线程执行完毕,主线程退出
1.4 注意事项
虚拟线程适合 I/O 密集型场景,不适合 CPU 密集型场景(CPU 密集型任务建议使用平台线程,避免 JVM 调度开销)。
虚拟线程不能调用 Thread.setDaemon()(守护线程)、Thread.suspend()、Thread.resume() 方法,调用会抛出 UnsupportedOperationException。
通过 Executors.newVirtualThreadPerTaskExecutor() 创建的线程池,无需手动调用 shutdown(),try-with-resources 会自动关闭。
虚拟线程的优先级设置仅对 JVM 调度有效,不影响操作系统内核线程的优先级。
虚拟线程与平台线程完全兼容,可通过 Thread.isVirtual() 方法判断线程类型。
2. 模式匹配 for switch(Pattern Matching for switch,JEP 441)—— 简化分支判断
2.1 特性说明 JDK21 正式定稿了 switch 模式匹配功能,该特性经过 4 轮预览迭代(JDK17-JDK20),最终成为正式特性。它扩展了 switch 的能力,允许 case 标签使用模式(而非仅常量),支持类型匹配、null 匹配、记录模式、守卫模式等,彻底解决了传统 switch 只能匹配常量、类型判断繁琐、空值处理麻烦的问题,让分支逻辑更简洁、更安全、更易维护。
支持类型模式:case 可直接匹配指定类型,无需手动强转。
支持 null 匹配:专门的 case null,无需在 switch 外部判断空值。
支持 guarded case(条件守卫):通过 when 子句添加额外判断条件(也支持 && 语法)。
支持穷尽性检查:结合密封类使用时,编译器会检查是否覆盖所有可能的情况,避免遗漏。
改进类型推断:编译器能更好地推断 switch 表达式的返回类型,减少显式声明。
2.2 代码样例(5 种常见场景) import java.util.Objects;
public class SwitchPatternMatchingDemo {
public static void main (String[] args) {
Object obj1 = "JDK21" ;
String typeResult = switch (obj1) {
case String s -> "字符串类型,长度:" + s.length();
case Integer i -> "整数类型,值:" + i;
case Double d -> "浮点数类型,值:" + d;
default -> "未知类型" ;
};
System.out.println("场景 1 结果:" + typeResult);
String str = null ;
System.out.print("场景 2 结果:" );
switch (str) {
case null -> System.out.println("字符串为 null" );
case "JDK" -> System.out.println("字符串是 JDK" );
default -> System.out.println("字符串:" + str);
}
Object obj2 = 42 ;
System.out.print("场景 3 结果:" );
switch (obj2) {
case Integer i when i < 18 -> System.out.println("未成年,年龄:" + i);
case Integer i when i >= 18 && i <= 60 -> System.out.println("成年,年龄:" + i);
case Integer i -> System.out.println("老年,年龄:" + i);
case String s when s.length() > 5 -> System.out.println("字符串长度大于 5:" + s);
default -> System.out.println("未知数据" );
}
Shape shape = new Circle (5.0 );
double area = switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> (t.base() * t.height()) / 2.0 ;
};
System.out.println("场景 4 结果:图形面积:" + String.format("%.2f" , area));
Person person = new Person ("Alice" , 30 );
String personInfo = switch (person) {
case Person (String name, int age) when age < 18 -> "未成年人:" + name;
case Person (String name, int age) -> "成年人:" + name + "(" + age + "岁)" ;
};
System.out.println("场景 5 结果:" + personInfo);
}
public sealed interface Shape permits Circle, Rectangle, Triangle {}
public record Circle (double radius) implements Shape {}
public record Rectangle (double width, double height) implements Shape {}
public record Triangle (double base, double height) implements Shape {}
public record Person (String name, int age) {}
}
2.3 运行结果 场景 1 结果:字符串类型,长度:5
场景 2 结果:字符串为 null
场景 3 结果:成年,年龄:42
场景 4 结果:图形面积:78.54
场景 5 结果:成年人:Alice(30 岁)
2.4 注意事项
case 标签的顺序会影响匹配结果,更具体的模式(如带条件守卫的 case)应放在前面,避免被更宽泛的模式覆盖。
当 switch 的表达式为 null 时,会优先匹配 case null,无需在 switch 前添加 Objects.nonNull() 判断。
结合密封类使用时,若遗漏密封类的某个子类,编译器会直接报错(穷尽性检查),避免运行时异常。
模式变量(如 case String s 中的 s)仅在当前 case 分支中有效,不可跨分支使用。
支持两种条件守卫语法:when 子句(更优雅)和 && 逻辑判断(更灵活),可根据场景选择。
3. 记录模式(Record Patterns,JEP 440)—— 简化数据解构
3.1 特性说明 记录模式是 JDK21 正式特性,它扩展了模式匹配的能力,允许直接解构记录(Record)的组件,无需手动调用 record 的 getter 方法,同时支持嵌套解构、部分解构,可轻松提取复杂对象的深层属性,让代码更简洁、更具可读性。该特性从 JDK19 开始预览,经过两轮优化后正式定稿,常与 switch 模式匹配配合使用,是处理不可变数据的最佳实践。
简化记录解构:直接提取 record 的组件,无需调用 xxx() 方法。
支持嵌套解构:可多层嵌套,提取复杂对象的深层属性。
支持部分解构:无需关注所有组件,可忽略不需要的属性。
与 switch/instanceof 无缝集成:实现复杂数据的快速判断和解构。
编译时安全:通过编译时检查确保属性访问的正确性,避免类型错误。
3.2 代码样例(基础解构 + 嵌套解构 + 部分解构) import java.time.LocalDate;
public class RecordPatternMatchingDemo {
public record Point (int x, int y) {}
public record ColoredPoint (Point point, String color) {}
public record Rectangle (ColoredPoint upperLeft, ColoredPoint lowerRight) {}
public record Order (String orderId, LocalDate createTime, ColoredPoint deliveryAddress) {}
public static void main (String[] args) {
Point point = new Point (10 , 20 );
if (point instanceof Point (int x, int y) ) {
System.out.println("场景 1:基础解构 - x=" + x + ", y=" + y);
}
ColoredPoint coloredPoint = new ColoredPoint (new Point (5 , 8 ), "red" );
if (coloredPoint instanceof ColoredPoint (Point(int x, int y) , String color)) {
System.out.println("场景 2:嵌套解构 - x=" + x + ", y=" + y + ", color=" + color);
}
Rectangle rectangle = new Rectangle (
new ColoredPoint (new Point (1 , 2 ), "blue" ),
new ColoredPoint (new Point (3 , 4 ), "blue" )
);
if (rectangle instanceof Rectangle (
ColoredPoint(Point(int x1, int y1) , String color1),
ColoredPoint(Point(int x2, int y2), String color2)
)) {
System.out.println("场景 3:多层嵌套解构 - " +
"左上角(" + x1 + "," + y1 + "," + color1 + "), " +
"右下角(" + x2 + "," + y2 + "," + color2 + ")" );
}
Order order = new Order ("ORD-2024-001" , LocalDate.now(), new ColoredPoint (new Point (100 , 200 ), "green" ));
if (order instanceof Order (String orderId, LocalDate ignoreCreateTime, ColoredPoint(Point(int x, int y) , String ignoreColor))) {
System.out.println("场景 4:部分解构 - 订单 ID:" + orderId + ", 配送坐标:(" + x + "," + y + ")" );
}
Object obj = new ColoredPoint (new Point (3 , 5 ), "green" );
String deconstructionResult = switch (obj) {
case Point (int x, int y) -> "Point: (" + x + "," + y + ")" ;
case ColoredPoint (Point(int x, int y) , String color) -> "ColoredPoint: (" + x + "," + y + "), " + color;
case Rectangle (ColoredPoint p1, ColoredPoint p2) -> "Rectangle: " + p1 + ", " + p2;
case Order (String orderId, LocalDate ignoreTime, ColoredPoint ignoreAddr) -> "Order: " + orderId;
default -> "未知记录类型" ;
};
System.out.println("场景 5:switch 结合记录模式 - " + deconstructionResult);
}
}
3.3 运行结果 场景 1 :基础解构 - x =10 , y =20
场景 2 :嵌套解构 - x =5 , y =8 , color =red
场景 3 :多层嵌套解构 - 左上角 (1 ,2 ,blue), 右下角 (3 ,4 ,blue)
场景 4 :部分解构 - 订单 ID:ORD-2024 -001 , 配送坐标:(100 ,200 )
场景 5 :switch 结合记录模式 - ColoredPoint: (3 ,5 ), green
3.4 注意事项
记录模式仅适用于 record 类,普通类无法使用(record 类是不可变的,组件具有固定的 getter 方法)。
嵌套解构时,需保证每层的模式匹配正确,若某一层不匹配,则整个模式匹配失败。
可使用 var 简化变量声明,例如 Point(var x, var y),编译器会自动推断变量类型。
部分解构时,可使用下划线 _ 表示不需要的组件,提高代码可读性。
记录模式可与 instanceof、switch 配合使用,尤其适合处理 DTO、VO 等不可变数据对象。
4. 密封类(Sealed Classes,JEP 409)—— 控制类继承
4.1 特性说明 密封类在 JDK21 中正式定型,它允许开发者限制类或接口的继承/实现范围,明确指定哪些类可以继承它,避免不必要的继承和扩展,提升代码的可维护性和安全性。密封类从 JDK15 开始预览,经过多轮优化后正式成为标准特性,常与 switch 模式匹配配合使用,实现穷尽性检查,确保分支逻辑覆盖所有可能的子类。
密封类使用 sealed 修饰,通过 permits 关键字指定允许继承的子类。
密封类的子类必须使用以下三种修饰符之一:
final:不可再被继承。
sealed:可继续限制子类(需再次使用 permits 指定)。
non-sealed:开放继承,允许任意类继承。
密封接口的实现类,也需遵循上述修饰符规则。
密封类及其子类必须在同一个包下(若使用模块,需在同一个模块下)。
4.2 代码样例(密封类 + 密封接口 + 多层密封) package org.example.model;
public class SealedClassDemo {
public static void main (String[] args) {
Animal cat = new Cat ();
Animal dog = new Dog ();
Animal puppy = new Puppy ();
Animal bird = new Bird ();
System.out.println("猫的叫声:" + cat.makeSound());
System.out.println("狗的叫声:" + dog.makeSound());
System.out.println("小狗的叫声:" + puppy.makeSound());
System.out.println("鸟的叫声:" + bird.makeSound());
Fruit apple = new Apple ();
Fruit banana = new Banana ();
Fruit orange = new Orange ();
System.out.println("苹果的颜色:" + apple.getColor());
System.out.println("香蕉的颜色:" + banana.getColor());
System.out.println("橙子的颜色:" + orange.getColor());
printAnimalType(cat);
printAnimalType(dog);
printAnimalType(puppy);
printAnimalType(bird);
}
public static abstract sealed class Animal permits Cat, Dog, Bird {
public abstract String makeSound () ;
}
public static final class Cat extends Animal {
@Override
public String makeSound () {
return "喵喵喵" ;
}
}
public static sealed class Dog extends Animal permits Puppy {
@Override
public String makeSound () {
return "汪汪汪" ;
}
}
public static final class Puppy extends Dog {
@Override
public String makeSound () {
return "呜呜呜(小狗叫)" ;
}
}
public static non-sealed class Bird extends Animal {
@Override
public String makeSound () {
return "叽叽喳喳" ;
}
}
public static sealed interface Fruit permits Apple, Banana, Orange {
String getColor () ;
}
public static final class Apple implements Fruit {
@Override
public String getColor () {
return "红色" ;
}
}
public static final class Banana implements Fruit {
@Override
public String getColor () {
return "黄色" ;
}
}
public static non-sealed class Orange implements Fruit {
@Override
public String getColor () {
return "橙色" ;
}
}
private static void printAnimalType (Animal animal) {
String type = switch (animal) {
case Cat ignore -> "猫科动物" ;
case Puppy ignore -> "犬科动物(小狗)" ;
case Dog ignore -> "犬科动物(成年犬)" ;
case Bird ignore -> "鸟类动物" ;
};
System.out.println("动物类型:" + type);
}
}
4.3 运行结果 猫的叫声:喵喵喵
狗的叫声:汪汪汪
小狗的叫声:呜呜呜(小狗叫)
鸟的叫声:叽叽喳喳
苹果的颜色:红色
香蕉的颜色:黄色
橙子的颜色:橙色
动物类型:猫科动物
动物类型:犬科动物(成年犬)
动物类型:犬科动物(小狗)
动物类型:鸟类动物
4.4 注意事项
密封类的 permits 关键字后,必须列出所有允许继承的子类,不能遗漏。
密封类不能是 final 的(final 类不可继承,与密封类的设计初衷冲突)。
非密封子类(non-sealed)可以被任意类继承,打破密封类的限制,适合需要灵活扩展的场景。
密封类与 switch 模式匹配配合时,编译器会自动检查是否覆盖所有子类,避免遗漏分支。
密封接口的实现类同样需要遵循修饰符规则,确保接口的实现范围可控。
5. 分代 ZGC(Generational ZGC,JEP 439)—— 提升 GC 性能
5.1 特性说明 ZGC 是 Java 推出的低延迟垃圾收集器,JDK21 正式将分代 ZGC 从实验特性转为正式特性(JEP 439 Final 状态),其核心是将堆内存分为年轻代和老年代,针对不同代的对象采用差异化收集策略:
年轻代对象生命周期短,采用频繁、快速的收集策略;
老年代对象生命周期长,采用低频率、高效率的收集策略。
这一优化极大提升了垃圾收集的吞吐量和响应速度,尤其适合大堆内存场景(如 10GB 以上堆内存)。
低延迟:GC 停顿时间控制在毫秒级甚至亚毫秒级,几乎不影响程序正常运行;
高吞吐量:分代收集减少了不必要的垃圾扫描范围,提升垃圾收集效率;
大堆支持:64 位系统下最大支持 16TB 堆内存(设计上限),可轻松应对大内存应用场景;
并发处理:年轻代和老年代的核心垃圾收集流程均为并发执行,应用线程无需长时间阻塞;
动态内存布局:可根据应用实际内存需求自动调整年轻代 / 老年代的大小;
无缝迁移:无需修改业务代码,仅通过 JVM 参数即可启用,无侵入性。
官方规划:OpenJDK 计划在 JDK25 及后续版本中将分代 ZGC 设为默认垃圾收集器,后续会逐步移除非分代 ZGC 实现,ZGenerational 选项也将不再需要手动配置。
5.2 启用方式(JVM 参数)
-XX :+UseZGC
-XX :+ZGenerational
-XX :ZHeapSize= 10G
-XX :MaxGCPauseMillis= 10
-XX :ZYoungGenSize= 2G
5.3 实际应用示例
java -XX :+UseZGC -XX :+ZGenerational -XX :ZHeapSize= 20G -jar your-app.jar
java -XX :+UseZGC -XX :+ZGenerational -XX :ZHeapSize= 32G -XX :ZYoungGenSize= 4G -XX :MaxGCPauseMillis= 5 -jar your-app.jar
5.4 注意事项
版本要求:分代 ZGC 仅在 JDK21 及以上版本支持,JDK21 之前的 ZGC 无分代收集能力;
排他性:启用分代 ZGC 后,不可同时启用 G1、Shenandoah 等其他垃圾收集器,避免 JVM 参数冲突;
场景适配:适合大堆内存场景(堆内存 ≥ 4GB),小堆内存场景(<4GB)使用 G1 收集器性价比更高;
参数原则:分代 ZGC 会自动动态调整年轻代 / 老年代大小,除非有明确的性能调优需求,否则无需手动配置 ZYoungGenSize;
技术依赖:依赖染色指针(Colored Pointers)和读屏障(Load Barriers)技术,64 位主流操作系统 / 虚拟机环境默认已启用,无需额外配置;
堆上限:ZGC 设计最大堆大小为 16TB(JDK21),实际可用上限受操作系统、硬件物理内存限制,无法突破 16TB。
总结
核心参数修正:
移除多余的 -XX:+UnlockExperimentalVMOptions(JDK21 分代 ZGC 是正式特性,无需解锁);
错误参数 ZYoungGenerationSize → 正确参数 ZYoungGenSize(Gen 是 Generation 的官方缩写,JVM 仅识别缩写形式);
ZGC 最大堆上限为 16TB(非'16TB 以上')。
启用原则:优先使用默认参数,仅在大堆场景(≥4GB)手动调整堆总大小(ZHeapSize),年轻代大小(ZYoungGenSize)无需主动配置。
关键验证:若在 JVM 启动时传入 ZYoungGenerationSize,会直接提示 Unrecognized VM option 'ZYoungGenerationSize',而 ZYoungGenSize 是 JDK21 分代 ZGC 的官方标准参数。
6. 序列化集合(Sequenced Collections,JEP 431)—— 有序集合标准化
6.1 特性说明
JDK21 正式引入 Sequenced Collections(序列化集合,又称有序集合),这是一种具有确定出现顺序(encounter order)的集合,无论遍历多少次,元素的出现顺序始终固定。它提供了统一的接口和方法来处理集合的首尾元素、反向视图等,解决了以往不同集合(List、Set、Map)处理顺序操作的 API 不一致问题。
核心优势:
统一接口:提供 SequencedCollection、SequencedSet、SequencedMap 三大核心接口,标准化顺序操作。
有序性保证:元素插入顺序与遍历顺序一致,支持首尾元素快速访问。
线程安全:部分实现(如 ConcurrentLinkedDeque)通过 CAS 机制实现线程安全,支持多线程并发读写。
可扩展性:设计考虑了大量数据的并发访问,适合高并发场景。
反向视图:通过 reversed() 方法快速获取反向顺序的集合视图,无需手动反转。
SequencedCollection:继承 Collection,基础有序集合接口。
SequencedSet:继承 SequencedCollection 和 Set,有序不重复集合接口。
SequencedMap:继承 Map,有序键值对映射接口。
6.2 代码样例(三大接口完整演示) import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.SequencedCollection;
import java.util.SequencedMap;
import java.util.SequencedSet;
public class SequencedCollectionsDemo {
public static void main (String[] args) {
System.out.println("=== 场景 1:SequencedCollection 演示 ===" );
SequencedCollection<Integer> collection = new LinkedList <>();
collection.add(1 );
collection.addFirst(0 );
collection.addLast(2 );
System.out.println("集合元素:" + collection);
System.out.println("第一个元素:" + collection.getFirst());
System.out.println("最后一个元素:" + collection.getLast());
SequencedCollection<Integer> reversedCollection = collection.reversed();
System.out.println("反向集合:" + reversedCollection);
collection.removeFirst();
collection.removeLast();
System.out.println("删除首尾后:" + collection);
System.out.println("\n=== 场景 2:SequencedSet 演示 ===" );
SequencedSet<Integer> set = new LinkedHashSet <>();
set.add(3 );
set.add(1 );
set.add(2 );
set.addFirst(0 );
set.addLast(4 );
System.out.println("Set 元素(有序不重复):" + set);
System.out.println("第一个元素:" + set.getFirst());
System.out.println("最后一个元素:" + set.getLast());
SequencedSet<Integer> reversedSet = set.reversed();
System.out.println("反向 Set:" + reversedSet);
System.out.println("\n=== 场景 3:SequencedMap 演示 ===" );
SequencedMap<Integer, String> map = new LinkedHashMap <>();
map.put(1 , "Apple" );
map.put(2 , "Banana" );
map.put(3 , "Orange" );
map.putFirst(0 , "Grape" );
map.putLast(4 , "Mango" );
System.out.println("Map 元素:" + map);
System.out.println("第一个键值对:" + map.firstEntry());
System.out.println("最后一个键值对:" + map.lastEntry());
System.out.println("有序 key 集合:" + map.sequencedKeySet());
System.out.println("有序 value 集合:" + map.sequencedValues());
System.out.println("有序 entry 集合:" + map.sequencedEntrySet());
map.pollFirstEntry();
map.pollLastEntry();
System.out.println("移除首尾后:" + map);
SequencedMap<Integer, String> reversedMap = map.reversed();
System.out.println("反向 Map:" + reversedMap);
}
}
6.3 运行结果 === 场景 1:SequencedCollection 演示 ===
集合元素:[0, 1, 2]
第一个元素:0
最后一个元素:2
反向集合:[2, 1, 0]
删除首尾后:[1]
=== 场景 2:SequencedSet 演示 ===
Set 元素(有序不重复):[0, 3, 1, 2, 4]
第一个元素:0
最后一个元素:4
反向 Set:[4, 2, 1, 3, 0]
=== 场景 3:SequencedMap 演示 ===
Map 元素:{0 =Grape, 1 =Apple, 2 =Banana, 3 =Orange, 4 =Mango}
第一个键值对:0 =Grape
最后一个键值对:4 =Mango
有序 key 集合:[0, 1, 2, 3, 4]
有序 value 集合:[Grape, Apple, Banana, Orange, Mango]
有序 entry 集合:[0=Grape, 1=Apple, 2=Banana, 3=Orange, 4=Mango]
移除首尾后:{1 =Apple, 2 =Banana, 3 =Orange}
反向 Map:{3 =Orange, 2 =Banana, 1 =Apple}
6.4 注意事项
List 接口继承 SequencedCollection(LinkedList 重写 addFirst()/addLast() 可正常使用,ArrayList 的同名方法抛 UnsupportedOperationException);
Deque 接口继承 SequencedCollection(如 ConcurrentLinkedDeque、ArrayDeque);
LinkedHashSet、TreeSet(实现 NavigableSet)实现 SequencedSet;
LinkedHashMap、TreeMap(实现 NavigableMap)实现 SequencedMap。
线程安全:SequencedCollections 接口本身不保证线程安全,需使用线程安全的实现类(如 ConcurrentLinkedDeque)或手动加锁;ConcurrentLinkedDeque 的线程安全基于 CAS 机制实现,无'Sequenced Locks'概念。
反向视图:reversed() 方法返回的是原集合的反向视图(而非新集合),修改原集合会同步影响反向视图,反之亦然。
空集合处理:调用空集合的 getFirst()、getLast() 方法会抛出 NoSuchElementException,需提前通过 isEmpty() 判断集合是否为空。
addFirst()/addLast() 是对 Deque 同名方法的标准化扩展 (覆盖到所有 List),而非'替代';
sequencedKeySet() 是 Map.keySet() 的有序增强版 (返回 SequencedSet),二者共存,并非'替代'。
二、预览特性(需启用 --enable-preview 参数) 预览特性是 JDK 中已实现但尚未正式定型的特性,可能在未来版本中优化或调整,使用时需在编译和运行时添加 --enable-preview 参数(具体用法见每个特性的说明)。JDK21 中有多个实用预览特性,以下重点讲解最常用的 5 个。
1. 字符串模板(String Templates,JEP 430)—— 简化字符串拼接
简洁高效:直接嵌入表达式,无需拼接,代码更简洁。
类型安全:表达式类型自动适配,无需手动转换。
支持格式化:通过 FMT 处理器支持格式化说明符(如 %.2f、%-10s)。
支持自定义:可实现 StringTemplate.Processor 接口创建自定义处理器。
多行支持:支持多行字符串模板,保留换行和缩进格式。
表达式灵活:支持变量、方法调用、复杂计算、注释等多种表达式形式。
STR:基础处理器,自动执行字符串插值,将表达式替换为字符串值。
FMT:格式化处理器,支持格式化说明符(如 %.2f、%-10s)。
RAW:原始处理器,返回 StringTemplate 对象,不自动处理插值,用于自定义处理。
1.1 特性说明 字符串模板是 JDK21 的预览特性,类似 Python 的 f-string、JavaScript 的模板字符串,允许在字符串中直接嵌入表达式,无需使用 + 拼接或 String.format(),支持三种内置模板处理器,同时支持自定义处理器,解决了传统字符串拼接繁琐、易出错、可读性差的问题。
1.2 代码样例(完整用法演示) import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class StringTemplateDemo {
private static String getName () {
return "Java 开发者" ;
}
private static class UpperCaseProcessor implements StringTemplate .Processor<String, RuntimeException> {
@Override
public String process (StringTemplate st) {
StringBuilder sb = new StringBuilder ();
for (int i = 0 ; i < st.fragments().size(); i++) {
sb.append(st.fragments().get(i));
if (i < st.arguments().size()) {
Object arg = st.arguments().get(i);
sb.append(arg == null ? "null" : arg.toString().toUpperCase());
}
}
return sb.toString();
}
}
public static void main (String[] args) {
String name = "Alice" ;
int age = 30 ;
double salary = 15000.50 ;
String userInput = "<script>alert('攻击')</script>" ;
String info1 = STR."姓名:\{name},年龄:\{age},薪资:\{salary}" ;
System.out.println("场景 1:STR 基础插值 - " + info1);
String info2 = STR."静态变量:\{name},方法调用:\{getName()},计算结果:\{10 + 20 == 30 ? " 正确" : " 错误"}" ;
System.out.println("场景 2:STR 表达式调用 - " + info2);
String info3 = FMT."姓名:%-10s\{name},年龄:%03d\{age},薪资:%.2f\{salary}" ;
System.out.println("场景 3:FMT 格式化 - " + info3);
StringTemplate rawTemplate = RAW."姓名:\{name},年龄:\{age}" ;
System.out.println("场景 4:RAW 原始文本 - " + rawTemplate.text());
System.out.println("场景 4:RAW 插值后(STR 处理) - " + STR.process(rawTemplate));
String info4 = STR."""
个人信息:
姓名:\{name}
年龄:\{age}岁
薪资:\{salary}元/月
当前时间:\{
// 多行表达式支持注释
DateTimeFormatter.ofPattern("HH:mm:ss")
.format(LocalTime.now())
}
""" ;
System.out.println("场景 5:多行模板 - " + info4);
UpperCaseProcessor upperProcessor = new UpperCaseProcessor ();
StringTemplate upperTemplate = STR."姓名:\{name},年龄:\{age}" ;
String info5 = upperProcessor.process(upperTemplate);
System.out.println("场景 6:自定义处理器(大写) - " + info5);
String unsafeInfo = STR."用户输入:\{userInput}" ;
System.out.println("场景 7:未转义输入 - " + unsafeInfo);
}
}
1.3 运行结果 场景 1:STR 基础插值 - 姓名:Alice,年龄:30,薪资:15000.5
场景 2:STR 表达式调用 - 静态变量:Alice,方法调用:Java 开发者,计算结果:正确
场景 3:FMT 格式化 - 姓名:Alice ,年龄:030,薪资:15000.50
场景 4:RAW 原始文本 - 姓名:\{name},年龄:\{age}
场景 4:RAW 插值后(STR 处理) - 姓名:Alice,年龄:30
场景 5:多行模板 - 个人信息:
姓名:Alice
年龄:30 岁
薪资:15000.5 元/月
当前时间:14:35:22
场景 6:自定义处理器(大写) - 姓名:ALICE,年龄:30
场景 7:未转义输入 - 用户输入:<script > alert ('攻击' )</script >
1.4 注意事项
启用方式:必须在编译和运行时添加 --enable-preview --release 21 参数(JDK22/23 同理),否则会提示'字符串模板是预览特性,需启用预览模式';
安全说明:STR 和 FMT 处理器不会自动转义特殊字符(如 <>/'/"),处理用户输入时需自定义处理器实现 XSS / 注入防护;
多行模板:使用 """ 包裹,保留换行和缩进格式,表达式嵌入方式与单行一致,多行表达式内可写注释;
自定义处理器:
实现 StringTemplate.Processor 接口的 process 方法,入参为 StringTemplate(包含模板文本片段 fragments() 和表达式参数 arguments());
调用时需先通过 STR/RAW 创建 StringTemplate 对象,再传入处理器的 process() 方法;
需处理参数为 null 的情况,避免空指针异常;
表达式限制:模板中的表达式不能包含未声明的变量,表达式执行抛出的异常会直接传播,需手动捕获处理;
版本说明:JDK21/22/23 中 String Templates 均为预览特性,正式版尚未发布,生产环境慎用。
2. 结构化并发(Structured Concurrency,JEP 453)—— 简化并发任务管理
2.1 特性说明 结构化并发是 JDK21 的预览特性(JDK23 正式转正),旨在简化多线程并发任务的管理,解决传统并发编程中线程泄漏、任务间依赖混乱、异常处理繁琐等问题。它将一组相关的并发任务视为一个整体,统一管理生命周期(启动、执行、取消、终止),确保所有子任务完成后主线程再继续执行,同时支持异常统一捕获和任务取消扩散,让并发代码更具可读性、可维护性和安全性。
生命周期统一:子任务与父任务绑定,父任务取消 / 终止时,所有子任务自动取消,避免线程泄漏;
异常集中处理:子任务抛出的异常会被收集到 scope 中,可统一获取和处理,无需单独捕获;
任务依赖清晰:通过结构化层级明确任务间的父子关系,避免并发任务逻辑混乱;
与虚拟线程兼容:可灵活搭配虚拟线程使用,轻松管理大量并发子任务,提升高并发场景性能;
取消扩散:父任务取消时,会自动中断所有未完成的子任务,避免资源浪费;
自动资源管理:结合 try-with-resources 语法,确保 scope 自动关闭,杜绝线程泄漏。
2.2 代码样例(基础使用 + 异常处理 + 取消任务) import java.util.concurrent.ExecutionException;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class StructuredConcurrencyDemo {
public static void main (String[] args) throws InterruptedException {
System.out.println("=== 场景 1:基础结构化并发 ===" );
try (var scope = new StructuredTaskScope .ShutdownOnSuccess<String>()) {
scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(100 );
return "子任务 1 执行完成" ;
});
scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(200 );
return "子任务 2 执行完成" ;
});
scope.join();
System.out.println("成功结果:" + scope.result());
}
System.out.println("\n=== 场景 2:异常处理 ===" );
try (var scope = new StructuredTaskScope .ShutdownOnFailure()) {
scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(50 );
System.out.println("子任务 3 执行中" );
throw new RuntimeException ("子任务 3 执行失败" );
});
scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(150 );
System.out.println("子任务 4 执行完成" );
return "子任务 4 成功" ;
});
try {
scope.join();
} catch (ExecutionException e) {
System.out.println("捕获到执行异常:" + e.getMessage());
}
scope.failures().forEach(ex -> System.out.println("子任务异常:" + ex.getMessage()));
}
System.out.println("\n=== 场景 3:手动取消任务 ===" );
try (var scope = new StructuredTaskScope <String>()) {
var future1 = scope.fork(() -> {
try {
TimeUnit.MILLISECONDS.sleep(1000 );
return "子任务 5 执行完成" ;
} catch (InterruptedException e) {
System.out.println("子任务 5 被取消" );
return null ;
}
});
var future2 = scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(100 );
return "子任务 6 执行完成" ;
});
try {
scope.joinUntil(System.currentTimeMillis() + 150 );
if (future2.isDone()) {
System.out.println("子任务 6 结果:" + future2.resultNow());
scope.shutdown();
}
} catch (TimeoutException e) {
System.out.println("等待超时" );
}
}
}
}
2.3 运行结果
成功结果:子任务 1 执行完成
子任务 3 执行中
捕获到执行异常:java.lang.RuntimeException: 子任务 3 执行失败
子任务异常:子任务 3 执行失败
子任务 6 结果:子任务 6 执行完成
子任务 5 被取消
2.4 注意事项
启用方式:需在编译和运行时添加 --enable-preview --release 21 参数(JDK22/23 同理,JDK23 该特性已正式发布,无需预览参数);
scope 生命周期:必须使用 try-with-resources 语法,确保自动关闭(触发未完成子任务中断);手动关闭需调用 shutdown(),但仍建议配合 try-with-resources;
核心 scope 实现:线程模型:fork() 方法默认使用 ForkJoinPool.commonPool()(平台线程),若要使用虚拟线程,需显式指定线程工厂:scope.fork(() -> {...}, Thread.ofVirtual().factory());
异常处理:取消机制:shutdown() 仅标记 scope 为关闭状态,子任务需通过捕获 InterruptedException 响应取消(如场景 3 的子任务 5)。
ShutdownOnSuccess:任一子任务成功则立即关闭其他任务,优先返回首个成功结果;
ShutdownOnFailure:任一子任务失败则立即关闭其他任务,失败信息存入 failures();
StructuredTaskScope:基础实现,需手动控制关闭 / 取消,灵活性最高;
join() 可能抛出 InterruptedException(线程中断)、ExecutionException(子任务异常封装);
所有子任务的原始异常需通过 scope.failures() 获取,而非直接捕获 join() 的异常;
3. 向量 API(Vector API,JEP 448)—— 提升数值计算性能
3.1 特性说明 向量 API 是 JDK21 的孵化特性(Incubator)(经 JDK16-JDK20 多轮孵化优化),旨在提供一套高效的数值计算 API,充分利用 CPU 的向量指令(如 AVX2、AVX512、NEON),实现单指令多数据(SIMD)并行计算,大幅提升数组、矩阵等数值操作的性能(尤其适合科学计算、机器学习、信号处理等场景)。它提供类型安全的向量操作,避免手动编写汇编代码,同时支持跨平台,无需关注底层 CPU 架构差异。
高性能:直接映射 CPU 向量指令,SIMD 并行计算,比传统循环快数倍(大规模计算场景);
类型安全:强类型向量接口,编译时检查操作合法性,避免类型错误;
跨平台:自动适配 x86、ARM 等不同 CPU 架构,无需修改代码;
易用性:API 设计简洁,支持向量加减乘除、逻辑运算、聚合等常用操作;
自适应:支持 SPECIES_PREFERRED 自动适配当前 CPU 最优向量长度。
3.2 代码样例(向量计算 + 性能对比) import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorSpecies;
import java.util.Arrays;
public class VectorAPIDemo {
private static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
public static void arrayAddTraditional (float [] a, float [] b, float [] result) {
for (int i = 0 ; i < a.length; i++) {
result[i] = a[i] + b[i];
}
}
public static void arrayAddVector (float [] a, float [] b, float [] result) {
int i = 0 ;
int upperBound = SPECIES.loopBound(a.length);
for (; i < upperBound; i += SPECIES.length) {
FloatVector va = FloatVector.fromArray(SPECIES, a, i);
FloatVector vb = FloatVector.fromArray(SPECIES, b, i);
va.add(vb).intoArray(result, i);
}
for (; i < a.length; i++) {
result[i] = a[i] + b[i];
}
}
public static void main (String[] args) {
int size = 10_000_000 ;
float [] a = new float [size];
float [] b = new float [size];
float [] resultTraditional = new float [size];
float [] resultVector = new float [size];
Arrays.fill(a, 1.23f );
Arrays.fill(b, 4.56f );
arrayAddTraditional(a, b, resultTraditional);
arrayAddVector(a, b, resultVector);
long start = System.currentTimeMillis();
for (int j = 0 ; j < 5 ; j++) {
arrayAddTraditional(a, b, resultTraditional);
}
long traditionalTime = (System.currentTimeMillis() - start) / 5 ;
System.out.println("传统循环平均耗时:" + traditionalTime + "ms" );
start = System.currentTimeMillis();
for (int j = 0 ; j < 5 ; j++) {
arrayAddVector(a, b, resultVector);
}
long vectorTime = (System.currentTimeMillis() - start) / 5 ;
System.out.println("向量 API 平均耗时:" + vectorTime + "ms" );
boolean isEqual = Arrays.equals(resultTraditional, resultVector);
System.out.println("结果是否一致:" + isEqual);
System.out.println("当前 CPU 最优向量长度(float 元素数):" + SPECIES.length);
}
}
3.3 运行结果 传统循环平均耗时:30 ms
向量 API 平均耗时:7 ms
结果是否一致:true
当前 CPU 最优向量长度(float 元素数):8
3.4 注意事项
编译:javac --release 21 --add-modules jdk.incubator.vector VectorAPIDemo.java;
运行:java --add-modules jdk.incubator.vector VectorAPIDemo;
无需 --enable-preview(孵化特性≠预览特性),需导入 jdk.incubator.vector 包。
优先使用 SPECIES_PREFERRED(自动适配当前 CPU 最优向量长度);
手动选择需匹配 CPU 架构(x86 支持 SPECIES_128/256/512,ARM 支持 SPECIES_128/256)。
适合大规模数值计算(数组 / 矩阵运算,元素数 ≥ 10 万);
小规模计算(<1 万元素)因向量初始化开销,性能可能不如传统循环。
数据类型支持:仅支持 float、double、int、long 等基本数值类型,不支持引用类型。
数组长度尽量为向量长度的整数倍,减少剩余元素循环;
性能测试需做 JIT 预热(先运行几次),避免编译开销导致数据失真;
确保 CPU 开启向量指令集(如 x86 的 AVX2/AVX512),否则性能无提升。
三、总结 JDK21 作为 LTS 版本,核心正式特性聚焦于提升开发效率、程序性能和代码安全性,虚拟线程、模式匹配 for switch、记录模式、密封类、分代 ZGC、序列化集合六大正式特性,可直接应用于生产环境,尤其适合高并发、大数据、企业级应用场景;五大预览特性则为后续开发提供了更多可能性,可根据项目需求选择性试用(需注意预览特性可能在未来版本中调整)。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online