Java?JDK21?新特性全面解析
JDK21 是 Oracle 發(fā)布的繼 JDK8、JDK11、JDK17 之后的第四個(gè)長(zhǎng)期支持版(LTS),于 2023 年 9 月 19 日正式發(fā)布,共包含 15 個(gè)新特性,涵蓋語(yǔ)言增強(qiáng)、性能優(yōu)化、API 擴(kuò)展等多個(gè)維度,極大提升了開(kāi)發(fā)效率、程序性能和代碼安全性。
提示:本文所有代碼均基于 JDK21 環(huán)境編寫(xiě),運(yùn)行前請(qǐng)確保已安裝 JDK21 并配置好環(huán)境變量;涉及預(yù)覽特性的代碼,需在編譯和運(yùn)行時(shí)添加 --enable-preview 參數(shù)(具體用法見(jiàn)對(duì)應(yīng)特性說(shuō)明)。
一、核心正式特性
1. 虛擬線程(Virtual Threads,JEP 444)—— 輕量級(jí)高并發(fā)利器
1.1 特性說(shuō)明
虛擬線程是 JDK21 最受關(guān)注的核心特性,它是由 JVM 管理的輕量級(jí)線程,而非操作系統(tǒng)內(nèi)核線程,徹底解決了傳統(tǒng)平臺(tái)線程(Platform Thread)資源占用高、上下文切換開(kāi)銷(xiāo)大的問(wèn)題。虛擬線程的內(nèi)存占用極低(每個(gè)僅需幾 KB),可輕松創(chuàng)建數(shù)百萬(wàn)個(gè)并發(fā)任務(wù),尤其適合 I/O 密集型場(chǎng)景(如接口調(diào)用、數(shù)據(jù)庫(kù)操作、文件讀寫(xiě)等),能極大提升程序吞吐量,且無(wú)需修改現(xiàn)有線程相關(guān)代碼,上手成本極低。
核心優(yōu)勢(shì):
- 輕量級(jí):內(nèi)存占用遠(yuǎn)低于平臺(tái)線程,單個(gè) JVM 可創(chuàng)建數(shù)百萬(wàn)個(gè)虛擬線程。
- 低開(kāi)銷(xiāo):上下文切換由 JVM 管理,無(wú)需操作系統(tǒng)內(nèi)核參與,開(kāi)銷(xiāo)極小。
- 易遷移:現(xiàn)有基于
Runnable、Callable的代碼可直接遷移,無(wú)需修改業(yè)務(wù)邏輯。 - 結(jié)構(gòu)化并發(fā):配合
Executors.newVirtualThreadPerTaskExecutor(),簡(jiǎn)化并發(fā)任務(wù)管理。 - 調(diào)度優(yōu)化:虛擬線程阻塞時(shí)會(huì)自動(dòng)切換到其他正在運(yùn)行的線程,充分利用 CPU 時(shí)間,避免資源閑置。
- 無(wú)規(guī)模限制:不受操作系統(tǒng)對(duì)線程數(shù)量的限制,理論上支持無(wú)限量創(chuàng)建(受限于內(nèi)存)。
1.2 代碼樣例(4種創(chuàng)建方式,推薦第三種)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
* 虛擬線程 4種創(chuàng)建方式演示
*/
public static void main(String[] args) throws InterruptedException {
// 方式1:通過(guò) Thread.startVirtualThread() 直接創(chuàng)建(最簡(jiǎn)單)
Thread.startVirtualThread(() -> {
System.out.println("方式1:虛擬線程運(yùn)行中,線程類(lèi)型:" + Thread.currentThread().getClass().getSimpleName());
try {
// 模擬I/O操作(睡眠100ms)
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("方式1:虛擬線程執(zhí)行完畢");
});
// 方式2:通過(guò) Thread.Builder 構(gòu)建(可設(shè)置線程名稱(chēng),移除不支持的優(yōu)先級(jí))
Thread virtualThread = Thread.ofVirtual()
.name("virtual-thread-2") // 設(shè)置線程名稱(chēng)(虛擬線程支持設(shè)置名稱(chēng))
.unstarted(() -> {
System.out.println("方式2:虛擬線程運(yùn)行中,線程名稱(chēng):" + Thread.currentThread().getName());
try {
TimeUnit.MILLISECONDS.sleep(150);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("方式2:虛擬線程執(zhí)行完畢");
});
virtualThread.start(); // 啟動(dòng)虛擬線程
// 方式3:通過(guò)線程池創(chuàng)建(推薦,適合批量管理并發(fā)任務(wù))
// newVirtualThreadPerTaskExecutor():每個(gè)任務(wù)對(duì)應(yīng)一個(gè)虛擬線程
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 5; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("方式3:虛擬線程任務(wù)" + taskId + "運(yùn)行中,線程ID:" + Thread.currentThread().threadId());
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("方式3:虛擬線程任務(wù)" + taskId + "執(zhí)行完畢");
});
}
} // try-with-resources 自動(dòng)關(guān)閉線程池,無(wú)需手動(dòng)shutdown
// 方式4:通過(guò) ThreadFactory 創(chuàng)建(靈活定制線程屬性)
// 替換 namePrefix 為 name(prefix, start),移除無(wú)效的 daemon 方法
ThreadFactory factory = Thread.ofVirtual()
.name("custom-virtual-thread-", 0) // 名稱(chēng)前綴 + 起始數(shù)字(0開(kāi)始)
.factory();
Thread customThread = factory.newThread(() -> {
System.out.println("方式4:自定義虛擬線程運(yùn)行中,線程名稱(chēng):" + Thread.currentThread().getName());
try {
TimeUnit.MILLISECONDS.sleep(250);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("方式4:自定義虛擬線程執(zhí)行完畢");
});
customThread.start();
// 等待所有虛擬線程執(zhí)行完畢(主線程阻塞,避免程序提前退出)
TimeUnit.SECONDS.sleep(1);
System.out.println("所有虛擬線程執(zhí)行完畢,主線程退出");
}1.3 運(yùn)行結(jié)果(關(guān)鍵信息)
方式1:虛擬線程運(yùn)行中,線程類(lèi)型:VirtualThread
方式2:虛擬線程運(yùn)行中,線程名稱(chēng):virtual-thread-2
方式3:虛擬線程任務(wù)3運(yùn)行中,線程ID:40
方式3:虛擬線程任務(wù)2運(yùn)行中,線程ID:39
方式3:虛擬線程任務(wù)4運(yùn)行中,線程ID:41
方式3:虛擬線程任務(wù)0運(yùn)行中,線程ID:37
方式3:虛擬線程任務(wù)1運(yùn)行中,線程ID:38
方式1:虛擬線程執(zhí)行完畢
方式2:虛擬線程執(zhí)行完畢
方式3:虛擬線程任務(wù)0執(zhí)行完畢
方式3:虛擬線程任務(wù)2執(zhí)行完畢
方式3:虛擬線程任務(wù)4執(zhí)行完畢
方式3:虛擬線程任務(wù)3執(zhí)行完畢
方式3:虛擬線程任務(wù)1執(zhí)行完畢
方式4:自定義虛擬線程運(yùn)行中,線程名稱(chēng):custom-virtual-thread-0
方式4:自定義虛擬線程執(zhí)行完畢
所有虛擬線程執(zhí)行完畢,主線程退出
1.4 注意事項(xiàng)
- 虛擬線程適合 I/O 密集型場(chǎng)景,不適合 CPU 密集型場(chǎng)景(CPU 密集型任務(wù)建議使用平臺(tái)線程,避免 JVM 調(diào)度開(kāi)銷(xiāo))。
- 虛擬線程不能調(diào)用
Thread.setDaemon()(守護(hù)線程)、Thread.suspend()、Thread.resume()方法,調(diào)用會(huì)拋出UnsupportedOperationException。 - 通過(guò)
Executors.newVirtualThreadPerTaskExecutor()創(chuàng)建的線程池,無(wú)需手動(dòng)調(diào)用shutdown(),try-with-resources 會(huì)自動(dòng)關(guān)閉。 - 虛擬線程的優(yōu)先級(jí)設(shè)置僅對(duì) JVM 調(diào)度有效,不影響操作系統(tǒng)內(nèi)核線程的優(yōu)先級(jí)。
- 虛擬線程與平臺(tái)線程完全兼容,可通過(guò)
Thread.isVirtual()方法判斷線程類(lèi)型。
2. 模式匹配 for switch(Pattern Matching for switch,JEP 441)—— 簡(jiǎn)化分支判斷
2.1 特性說(shuō)明
JDK21 正式定稿了 switch 模式匹配功能,該特性經(jīng)過(guò) 4 輪預(yù)覽迭代(JDK17-JDK20),最終成為正式特性。它擴(kuò)展了 switch 的能力,允許 case 標(biāo)簽使用模式(而非僅常量),支持類(lèi)型匹配、null 匹配、記錄模式、守衛(wèi)模式等,徹底解決了傳統(tǒng) switch 只能匹配常量、類(lèi)型判斷繁瑣、空值處理麻煩的問(wèn)題,讓分支邏輯更簡(jiǎn)潔、更安全、更易維護(hù)。
核心增強(qiáng)點(diǎn):
- 支持類(lèi)型模式:case 可直接匹配指定類(lèi)型,無(wú)需手動(dòng)強(qiáng)轉(zhuǎn)。
- 支持 null 匹配:專(zhuān)門(mén)的
case null,無(wú)需在 switch 外部判斷空值。 - 支持 guarded case(條件守衛(wèi)):通過(guò)
when子句添加額外判斷條件(也支持&& 語(yǔ)法)。 - 支持窮盡性檢查:結(jié)合密封類(lèi)使用時(shí),編譯器會(huì)檢查是否覆蓋所有可能的情況,避免遺漏。
- 改進(jìn)類(lèi)型推斷:編譯器能更好地推斷 switch 表達(dá)式的返回類(lèi)型,減少顯式聲明。
2.2 代碼樣例(5種常見(jiàn)場(chǎng)景)
import java.util.Objects;
/**
* switch 模式匹配 5種常見(jiàn)場(chǎng)景演示
* 要求:Java 21+ 版本運(yùn)行(所有特性均為正式特性)
*/
public class SwitchPatternMatchingDemo {
public static void main(String[] args) {
// 場(chǎng)景1:類(lèi)型模式匹配(替代 instanceof + 強(qiáng)轉(zhuǎn))
Object obj1 = "JDK21";
String typeResult = switch (obj1) {
case String s -> "字符串類(lèi)型,長(zhǎng)度:" + s.length();
case Integer i -> "整數(shù)類(lèi)型,值:" + i;
case Double d -> "浮點(diǎn)數(shù)類(lèi)型,值:" + d;
default -> "未知類(lèi)型";
};
System.out.println("場(chǎng)景1結(jié)果:" + typeResult);
// 場(chǎng)景2:null 匹配(無(wú)需外部判斷空值)
String str = null;
System.out.print("場(chǎng)景2結(jié)果:");
switch (str) {
case null -> System.out.println("字符串為null");
case "JDK" -> System.out.println("字符串是JDK");
default -> System.out.println("字符串:" + str);
}
// 場(chǎng)景3:條件守衛(wèi)(統(tǒng)一使用 when 子句,修復(fù) && 語(yǔ)法錯(cuò)誤)
Object obj2 = 42;
System.out.print("場(chǎng)景3結(jié)果:");
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("字符串長(zhǎng)度大于5:" + s);
default -> System.out.println("未知數(shù)據(jù)");
}
// 場(chǎng)景4:結(jié)合密封類(lèi)(窮盡性檢查,編譯器自動(dòng)校驗(yàn))
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;
// 無(wú)需default,編譯器會(huì)檢查密封類(lèi)的所有子類(lèi)是否都被覆蓋
};
System.out.println("場(chǎng)景4結(jié)果:圖形面積:" + String.format("%.2f", area));
// 場(chǎng)景5:記錄模式匹配(直接解構(gòu)記錄屬性)
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("場(chǎng)景5結(jié)果:" + personInfo);
}
// 密封類(lèi):限制子類(lèi)只能是指定的幾個(gè)類(lèi)(配合switch實(shí)現(xiàn)窮盡性檢查)
public sealed interface Shape permits Circle, Rectangle, Triangle {}
// 密封類(lèi)的子類(lèi),必須使用final、sealed或non-sealed修飾(record默認(rèn)final,符合要求)
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 {}
// 記錄類(lèi):用于演示記錄模式匹配
public record Person(String name, int age) {}
}2.3 運(yùn)行結(jié)果
場(chǎng)景1結(jié)果:字符串類(lèi)型,長(zhǎng)度:5
場(chǎng)景2結(jié)果:字符串為null
場(chǎng)景3結(jié)果:成年,年齡:42
場(chǎng)景4結(jié)果:圖形面積:78.54
場(chǎng)景5結(jié)果:成年人:Alice(30歲)
2.4 注意事項(xiàng)
- case 標(biāo)簽的順序會(huì)影響匹配結(jié)果,更具體的模式(如帶條件守衛(wèi)的 case)應(yīng)放在前面,避免被更寬泛的模式覆蓋。
- 當(dāng) switch 的表達(dá)式為 null 時(shí),會(huì)優(yōu)先匹配
case null,無(wú)需在 switch 前添加Objects.nonNull()判斷。 - 結(jié)合密封類(lèi)使用時(shí),若遺漏密封類(lèi)的某個(gè)子類(lèi),編譯器會(huì)直接報(bào)錯(cuò)(窮盡性檢查),避免運(yùn)行時(shí)異常。
- 模式變量(如 case String s 中的 s)僅在當(dāng)前 case 分支中有效,不可跨分支使用。
- 支持兩種條件守衛(wèi)語(yǔ)法:
when子句(更優(yōu)雅)和&& 邏輯判斷(更靈活),可根據(jù)場(chǎng)景選擇。
3. 記錄模式(Record Patterns,JEP 440)—— 簡(jiǎn)化數(shù)據(jù)解構(gòu)
3.1 特性說(shuō)明
記錄模式是 JDK21 正式特性,它擴(kuò)展了模式匹配的能力,允許直接解構(gòu)記錄(Record)的組件,無(wú)需手動(dòng)調(diào)用 record 的 getter 方法,同時(shí)支持嵌套解構(gòu)、部分解構(gòu),可輕松提取復(fù)雜對(duì)象的深層屬性,讓代碼更簡(jiǎn)潔、更具可讀性。該特性從 JDK19 開(kāi)始預(yù)覽,經(jīng)過(guò)兩輪優(yōu)化后正式定稿,常與 switch 模式匹配配合使用,是處理不可變數(shù)據(jù)的最佳實(shí)踐。
核心價(jià)值:
- 簡(jiǎn)化記錄解構(gòu):直接提取 record 的組件,無(wú)需調(diào)用
xxx()方法。 - 支持嵌套解構(gòu):可多層嵌套,提取復(fù)雜對(duì)象的深層屬性。
- 支持部分解構(gòu):無(wú)需關(guān)注所有組件,可忽略不需要的屬性。
- 與 switch/instanceof 無(wú)縫集成:實(shí)現(xiàn)復(fù)雜數(shù)據(jù)的快速判斷和解構(gòu)。
- 編譯時(shí)安全:通過(guò)編譯時(shí)檢查確保屬性訪問(wèn)的正確性,避免類(lèi)型錯(cuò)誤。
3.2 代碼樣例(基礎(chǔ)解構(gòu) + 嵌套解構(gòu) + 部分解構(gòu))
import java.time.LocalDate;
/**
* 記錄(Record)模式匹配 - 解構(gòu)演示(修復(fù)未命名模式兼容問(wèn)題)
* 要求:Java 21+
*/
public class RecordPatternMatchingDemo {
// 基礎(chǔ)記錄(無(wú)嵌套)
public record Point(int x, int y) {}
// 嵌套記錄(包含Point記錄)
public record ColoredPoint(Point point, String color) {}
// 多層嵌套記錄(包含ColoredPoint記錄)
public record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
// 復(fù)雜業(yè)務(wù)記錄(包含嵌套記錄和普通類(lèi)型)
public record Order(String orderId, LocalDate createTime, ColoredPoint deliveryAddress) {}
public static void main(String[] args) {
// 場(chǎng)景1:基礎(chǔ)記錄解構(gòu)(直接提取Point的x和y)
Point point = new Point(10, 20);
if (point instanceof Point(int x, int y)) {
System.out.println("場(chǎng)景1:基礎(chǔ)解構(gòu) - x=" + x + ", y=" + y);
}
// 場(chǎng)景2:嵌套記錄解構(gòu)(提取ColoredPoint中的Point組件)
ColoredPoint coloredPoint = new ColoredPoint(new Point(5, 8), "red");
if (coloredPoint instanceof ColoredPoint(Point(int x, int y), String color)) {
System.out.println("場(chǎng)景2:嵌套解構(gòu) - x=" + x + ", y=" + y + ", color=" + color);
}
// 場(chǎng)景3:多層嵌套解構(gòu)(提取Rectangle中的深層Point組件)
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("場(chǎng)景3:多層嵌套解構(gòu) - " +
"左上角(" + x1 + "," + y1 + "," + color1 + "), " +
"右下角(" + x2 + "," + y2 + "," + color2 + ")");
}
// 場(chǎng)景4:部分解構(gòu)(替換_為顯式命名的忽略變量,解決兼容問(wèn)題)
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))) {
// 忽略ignoreCreateTime和ignoreColor,僅使用需要的變量
System.out.println("場(chǎng)景4:部分解構(gòu) - 訂單ID:" + orderId + ", 配送坐標(biāo):(" + x + "," + y + ")");
}
// 場(chǎng)景5:結(jié)合switch模式匹配(同樣替換_為ignore變量)
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 -> "未知記錄類(lèi)型";
};
System.out.println("場(chǎng)景5:switch結(jié)合記錄模式 - " + deconstructionResult);
}
}3.3 運(yùn)行結(jié)果
場(chǎng)景1:基礎(chǔ)解構(gòu) - x=10, y=20
場(chǎng)景2:嵌套解構(gòu) - x=5, y=8, color=red
場(chǎng)景3:多層嵌套解構(gòu) - 左上角(1,2,blue), 右下角(3,4,blue)
場(chǎng)景4:部分解構(gòu) - 訂單ID:ORD-2024-001, 配送坐標(biāo):(100,200)
場(chǎng)景5:switch結(jié)合記錄模式 - ColoredPoint: (3,5), green
3.4 注意事項(xiàng)
- 記錄模式僅適用于 record 類(lèi),普通類(lèi)無(wú)法使用(record 類(lèi)是不可變的,組件具有固定的 getter 方法)。
- 嵌套解構(gòu)時(shí),需保證每層的模式匹配正確,若某一層不匹配,則整個(gè)模式匹配失敗。
- 可使用
var簡(jiǎn)化變量聲明,例如Point(var x, var y),編譯器會(huì)自動(dòng)推斷變量類(lèi)型。 - 部分解構(gòu)時(shí),可使用下劃線
_表示不需要的組件,提高代碼可讀性。 - 記錄模式可與 instanceof、switch 配合使用,尤其適合處理 DTO、VO 等不可變數(shù)據(jù)對(duì)象。
4. 密封類(lèi)(Sealed Classes,JEP 409)—— 控制類(lèi)繼承
4.1 特性說(shuō)明
密封類(lèi)在 JDK21 中正式定型,它允許開(kāi)發(fā)者限制類(lèi)或接口的繼承/實(shí)現(xiàn)范圍,明確指定哪些類(lèi)可以繼承它,避免不必要的繼承和擴(kuò)展,提升代碼的可維護(hù)性和安全性。密封類(lèi)從 JDK15 開(kāi)始預(yù)覽,經(jīng)過(guò)多輪優(yōu)化后正式成為標(biāo)準(zhǔn)特性,常與 switch 模式匹配配合使用,實(shí)現(xiàn)窮盡性檢查,確保分支邏輯覆蓋所有可能的子類(lèi)。
核心規(guī)則:
- 密封類(lèi)使用
sealed修飾,通過(guò)permits關(guān)鍵字指定允許繼承的子類(lèi)。 - 密封類(lèi)的子類(lèi)必須使用以下三種修飾符之一:
final:不可再被繼承。sealed:可繼續(xù)限制子類(lèi)(需再次使用 permits 指定)。non-sealed:開(kāi)放繼承,允許任意類(lèi)繼承。
- 密封接口的實(shí)現(xiàn)類(lèi),也需遵循上述修飾符規(guī)則。
- 密封類(lèi)及其子類(lèi)必須在同一個(gè)包下(若使用模塊,需在同一個(gè)模塊下)。
4.2 代碼樣例(密封類(lèi) + 密封接口 + 多層密封)
package org.example.model;
/**
* 密封類(lèi) + 密封接口 + 多層密封 演示
* 要求:Java 21+
*/
public class SealedClassDemo {
public static void main(String[] args) {
// 測(cè)試密封類(lèi)(靜態(tài)內(nèi)部類(lèi)可直接實(shí)例化)
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("鳥(niǎo)的叫聲:" + bird.makeSound());
// 測(cè)試密封接口
Fruit apple = new Apple();
Fruit banana = new Banana();
Fruit orange = new Orange();
System.out.println("蘋(píng)果的顏色:" + apple.getColor());
System.out.println("香蕉的顏色:" + banana.getColor());
System.out.println("橙子的顏色:" + orange.getColor());
// 測(cè)試窮盡性檢查(switch + 密封類(lèi))
printAnimalType(cat);
printAnimalType(dog);
printAnimalType(puppy);
printAnimalType(bird);
}
// 給sealed類(lèi)添加abstract修飾符(包含抽象方法必須聲明為抽象類(lèi))
// 密封+抽象類(lèi):僅允許 Cat、Dog、Bird 繼承
public static abstract sealed class Animal permits Cat, Dog, Bird {
public abstract String makeSound();
}
// final 子類(lèi):不可再繼承
public static final class Cat extends Animal {
@Override
public String makeSound() {
return "喵喵喵";
}
}
// sealed 子類(lèi):可繼續(xù)限制子類(lèi)(僅允許 Puppy 繼承)
public static sealed class Dog extends Animal permits Puppy {
@Override
public String makeSound() {
return "汪汪汪";
}
}
// Dog 的子類(lèi),final 修飾
public static final class Puppy extends Dog {
@Override
public String makeSound() {
return "嗚嗚嗚(小狗叫)";
}
}
// non-sealed 子類(lèi):開(kāi)放繼承,允許任意類(lèi)繼承
public static non-sealed class Bird extends Animal {
@Override
public String makeSound() {
return "嘰嘰喳喳";
}
}
// 密封接口:僅允許 Apple、Banana、Orange 實(shí)現(xiàn)
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 "橙色";
}
}
// 結(jié)合switch實(shí)現(xiàn)窮盡性檢查
// 調(diào)整匹配順序(先匹配Puppy,再匹配Dog),讓編譯器識(shí)別窮盡性
private static void printAnimalType(Animal animal) {
String type = switch (animal) {
case Cat ignore -> "貓科動(dòng)物";
case Puppy ignore -> "犬科動(dòng)物(小狗)"; // 先匹配Puppy(Dog的子類(lèi))
case Dog ignore -> "犬科動(dòng)物(成年犬)"; // 再匹配普通Dog
case Bird ignore -> "鳥(niǎo)類(lèi)動(dòng)物";
};
System.out.println("動(dòng)物類(lèi)型:" + type);
}
}4.3 運(yùn)行結(jié)果
貓的叫聲:喵喵喵
狗的叫聲:汪汪汪
小狗的叫聲:?jiǎn)鑶鑶瑁ㄐ」方校?br />鳥(niǎo)的叫聲:嘰嘰喳喳
蘋(píng)果的顏色:紅色
香蕉的顏色:黃色
橙子的顏色:橙色
動(dòng)物類(lèi)型:貓科動(dòng)物
動(dòng)物類(lèi)型:犬科動(dòng)物(成年犬)
動(dòng)物類(lèi)型:犬科動(dòng)物(小狗)
動(dòng)物類(lèi)型:鳥(niǎo)類(lèi)動(dòng)物
4.4 注意事項(xiàng)
- 密封類(lèi)的
permits關(guān)鍵字后,必須列出所有允許繼承的子類(lèi),不能遺漏。 - 密封類(lèi)不能是
final的(final 類(lèi)不可繼承,與密封類(lèi)的設(shè)計(jì)初衷沖突)。 - 非密封子類(lèi)(non-sealed)可以被任意類(lèi)繼承,打破密封類(lèi)的限制,適合需要靈活擴(kuò)展的場(chǎng)景。
- 密封類(lèi)與 switch 模式匹配配合時(shí),編譯器會(huì)自動(dòng)檢查是否覆蓋所有子類(lèi),避免遺漏分支。
- 密封接口的實(shí)現(xiàn)類(lèi)同樣需要遵循修飾符規(guī)則,確保接口的實(shí)現(xiàn)范圍可控。
5. 分代 ZGC(Generational ZGC,JEP 439)—— 提升 GC 性能
5.1 特性說(shuō)明
ZGC 是 Java 推出的低延遲垃圾收集器,JDK21 正式將分代 ZGC 從實(shí)驗(yàn)特性轉(zhuǎn)為正式特性(JEP 439 Final 狀態(tài)),其核心是將堆內(nèi)存分為年輕代和老年代,針對(duì)不同代的對(duì)象采用差異化收集策略:
- 年輕代對(duì)象生命周期短,采用頻繁、快速的收集策略;
- 老年代對(duì)象生命周期長(zhǎng),采用低頻率、高效率的收集策略。
這一優(yōu)化極大提升了垃圾收集的吞吐量和響應(yīng)速度,尤其適合大堆內(nèi)存場(chǎng)景(如 10GB 以上堆內(nèi)存)。
核心優(yōu)勢(shì):
- 低延遲:GC 停頓時(shí)間控制在毫秒級(jí)甚至亞毫秒級(jí),幾乎不影響程序正常運(yùn)行;
- 高吞吐量:分代收集減少了不必要的垃圾掃描范圍,提升垃圾收集效率;
- 大堆支持:64 位系統(tǒng)下最大支持 16TB 堆內(nèi)存(設(shè)計(jì)上限),可輕松應(yīng)對(duì)大內(nèi)存應(yīng)用場(chǎng)景;
- 并發(fā)處理:年輕代和老年代的核心垃圾收集流程均為并發(fā)執(zhí)行,應(yīng)用線程無(wú)需長(zhǎng)時(shí)間阻塞;
- 動(dòng)態(tài)內(nèi)存布局:可根據(jù)應(yīng)用實(shí)際內(nèi)存需求自動(dòng)調(diào)整年輕代 / 老年代的大??;
- 無(wú)縫遷移:無(wú)需修改業(yè)務(wù)代碼,僅通過(guò) JVM 參數(shù)即可啟用,無(wú)侵入性。
官方規(guī)劃:OpenJDK 計(jì)劃在 JDK25 及后續(xù)版本中將分代 ZGC 設(shè)為默認(rèn)垃圾收集器,后續(xù)會(huì)逐步移除非分代 ZGC 實(shí)現(xiàn),ZGenerational 選項(xiàng)也將不再需要手動(dòng)配置。
5.2 啟用方式(JVM 參數(shù))
# 啟用分代 ZGC(JDK21 正式特性,無(wú)需解鎖實(shí)驗(yàn)性選項(xiàng)) -XX:+UseZGC -XX:+ZGenerational # 可選參數(shù)(優(yōu)化大堆場(chǎng)景) -XX:ZHeapSize=10G # 設(shè)置堆內(nèi)存總大小為10G -XX:MaxGCPauseMillis=10 # 建議最大GC停頓時(shí)間(ZGC 會(huì)盡力滿足,非強(qiáng)制) -XX:ZYoungGenSize=2G # 設(shè)置年輕代初始大小為2G(默認(rèn)自動(dòng)動(dòng)態(tài)調(diào)整,無(wú)需手動(dòng)配置)
5.3 實(shí)際應(yīng)用示例
# 運(yùn)行Spring Boot應(yīng)用并啟用分代ZGC(JDK21+) java -XX:+UseZGC -XX:+ZGenerational -XX:ZHeapSize=20G -jar your-app.jar # 進(jìn)階配置(指定年輕代大小+停頓時(shí)間目標(biāo)) java -XX:+UseZGC -XX:+ZGenerational -XX:ZHeapSize=32G -XX:ZYoungGenSize=4G -XX:MaxGCPauseMillis=5 -jar your-app.jar
5.4 注意事項(xiàng)
- 版本要求:分代 ZGC 僅在 JDK21 及以上版本支持,JDK21 之前的 ZGC 無(wú)分代收集能力;
- 排他性:?jiǎn)⒂梅执?ZGC 后,不可同時(shí)啟用 G1、Shenandoah 等其他垃圾收集器,避免 JVM 參數(shù)沖突;
- 場(chǎng)景適配:適合大堆內(nèi)存場(chǎng)景(堆內(nèi)存 ≥ 4GB),小堆內(nèi)存場(chǎng)景(<4GB)使用 G1 收集器性價(jià)比更高;
- 參數(shù)原則:分代 ZGC 會(huì)自動(dòng)動(dòng)態(tài)調(diào)整年輕代 / 老年代大小,除非有明確的性能調(diào)優(yōu)需求,否則無(wú)需手動(dòng)配置
ZYoungGenSize; - 技術(shù)依賴(lài):依賴(lài)染色指針(Colored Pointers)和讀屏障(Load Barriers)技術(shù),64 位主流操作系統(tǒng) / 虛擬機(jī)環(huán)境默認(rèn)已啟用,無(wú)需額外配置;
- 堆上限:ZGC 設(shè)計(jì)最大堆大小為 16TB(JDK21),實(shí)際可用上限受操作系統(tǒng)、硬件物理內(nèi)存限制,無(wú)法突破 16TB。
總結(jié)
- 核心參數(shù)修正:
- 移除多余的
-XX:+UnlockExperimentalVMOptions(JDK21 分代 ZGC 是正式特性,無(wú)需解鎖); - 錯(cuò)誤參數(shù)
ZYoungGenerationSize→ 正確參數(shù)ZYoungGenSize(Gen是Generation的官方縮寫(xiě),JVM 僅識(shí)別縮寫(xiě)形式); - ZGC 最大堆上限為 16TB(非 “16TB 以上”)。
- 移除多余的
- 啟用原則:優(yōu)先使用默認(rèn)參數(shù),僅在大堆場(chǎng)景(≥4GB)手動(dòng)調(diào)整堆總大?。?code>ZHeapSize),年輕代大?。?code>ZYoungGenSize)無(wú)需主動(dòng)配置。
- 關(guān)鍵驗(yàn)證:若在 JVM 啟動(dòng)時(shí)傳入
ZYoungGenerationSize,會(huì)直接提示Unrecognized VM option 'ZYoungGenerationSize',而ZYoungGenSize是 JDK21 分代 ZGC 的官方標(biāo)準(zhǔn)參數(shù)。
6. 序列化集合(Sequenced Collections,JEP 431)—— 有序集合標(biāo)準(zhǔn)化
6.1 特性說(shuō)明
- JDK21 正式引入 Sequenced Collections(序列化集合,又稱(chēng)有序集合),這是一種具有確定出現(xiàn)順序(encounter order)的集合,無(wú)論遍歷多少次,元素的出現(xiàn)順序始終固定。它提供了統(tǒng)一的接口和方法來(lái)處理集合的首尾元素、反向視圖等,解決了以往不同集合(List、Set、Map)處理順序操作的 API 不一致問(wèn)題。
- 核心優(yōu)勢(shì):
- 核心接口關(guān)系:
- 統(tǒng)一接口:提供
SequencedCollection、SequencedSet、SequencedMap三大核心接口,標(biāo)準(zhǔn)化順序操作。 - 有序性保證:元素插入順序與遍歷順序一致,支持首尾元素快速訪問(wèn)。
- 線程安全:部分實(shí)現(xiàn)(如
ConcurrentLinkedDeque)通過(guò) Sequenced Locks 機(jī)制實(shí)現(xiàn)線程安全,支持多線程并發(fā)讀寫(xiě)。 - 可擴(kuò)展性:設(shè)計(jì)考慮了大量數(shù)據(jù)的并發(fā)訪問(wèn),適合高并發(fā)場(chǎng)景。
- 反向視圖:通過(guò)
reversed()方法快速獲取反向順序的集合視圖,無(wú)需手動(dòng)反轉(zhuǎn)。SequencedCollection:繼承Collection,基礎(chǔ)有序集合接口。SequencedSet:繼承SequencedCollection和Set,有序不重復(fù)集合接口。SequencedMap:繼承Map,有序鍵值對(duì)映射接口。
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;
/**
* 有序集合(Sequenced Collections)三大接口演示
* 注意:使用LinkedList(而非ArrayList),因?yàn)槠渲С謅ddFirst()/addLast()
*/
public class SequencedCollectionsDemo {
public static void main(String[] args) {
// 場(chǎng)景1:SequencedCollection 演示(以 LinkedList 為例,支持addFirst/addLast)
System.out.println("=== 場(chǎng)景1:SequencedCollection 演示 ===");
SequencedCollection<Integer> collection = new LinkedList<>();
collection.add(1);
collection.addFirst(0); // 添加到頭部(LinkedList支持,ArrayList不支持)
collection.addLast(2); // 添加到尾部
System.out.println("集合元素:" + collection);
System.out.println("第一個(gè)元素:" + collection.getFirst());
System.out.println("最后一個(gè)元素:" + collection.getLast());
SequencedCollection<Integer> reversedCollection = collection.reversed();
System.out.println("反向集合:" + reversedCollection);
collection.removeFirst(); // 刪除頭部元素
collection.removeLast(); // 刪除尾部元素
System.out.println("刪除首尾后:" + collection);
// 場(chǎng)景2:SequencedSet 演示(以 LinkedHashSet 為例)
System.out.println("\n=== 場(chǎng)景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元素(有序不重復(fù)):" + set);
System.out.println("第一個(gè)元素:" + set.getFirst());
System.out.println("最后一個(gè)元素:" + set.getLast());
SequencedSet<Integer> reversedSet = set.reversed();
System.out.println("反向Set:" + reversedSet);
// 場(chǎng)景3:SequencedMap 演示(以 LinkedHashMap 為例)
System.out.println("\n=== 場(chǎng)景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("第一個(gè)鍵值對(duì):" + map.firstEntry());
System.out.println("最后一個(gè)鍵值對(duì):" + map.lastEntry());
// 獲取有序的key、value、entry集合
System.out.println("有序key集合:" + map.sequencedKeySet());
System.out.println("有序value集合:" + map.sequencedValues());
System.out.println("有序entry集合:" + map.sequencedEntrySet());
// 移除首尾鍵值對(duì)
map.pollFirstEntry(); // 移除并返回第一個(gè)鍵值對(duì)
map.pollLastEntry(); // 移除并返回最后一個(gè)鍵值對(duì)
System.out.println("移除首尾后:" + map);
// 反向Map
SequencedMap<Integer, String> reversedMap = map.reversed();
System.out.println("反向Map:" + reversedMap);
}
}6.3 運(yùn)行結(jié)果
=== 場(chǎng)景1:SequencedCollection 演示 ===
集合元素:[0, 1, 2]
第一個(gè)元素:0
最后一個(gè)元素:2
反向集合:[2, 1, 0]
刪除首尾后:[1]=== 場(chǎng)景2:SequencedSet 演示 ===
Set元素(有序不重復(fù)):[0, 3, 1, 2, 4]
第一個(gè)元素:0
最后一個(gè)元素:4
反向Set:[4, 2, 1, 3, 0]=== 場(chǎng)景3:SequencedMap 演示 ===
Map元素:{0=Grape, 1=Apple, 2=Banana, 3=Orange, 4=Mango}
第一個(gè)鍵值對(duì):0=Grape
最后一個(gè)鍵值對(duì):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 注意事項(xiàng)
實(shí)現(xiàn)類(lèi)說(shuō)明:
List接口繼承SequencedCollection(LinkedList重寫(xiě)addFirst()/addLast()可正常使用,ArrayList的同名方法拋UnsupportedOperationException);Deque接口繼承SequencedCollection(如ConcurrentLinkedDeque、ArrayDeque);LinkedHashSet、TreeSet(實(shí)現(xiàn)NavigableSet)實(shí)現(xiàn)SequencedSet;LinkedHashMap、TreeMap(實(shí)現(xiàn)NavigableMap)實(shí)現(xiàn)SequencedMap。
線程安全:SequencedCollections 接口本身不保證線程安全,需使用線程安全的實(shí)現(xiàn)類(lèi)(如 ConcurrentLinkedDeque)或手動(dòng)加鎖;ConcurrentLinkedDeque 的線程安全基于 CAS 機(jī)制實(shí)現(xiàn),無(wú) “Sequenced Locks” 概念。
反向視圖:reversed() 方法返回的是原集合的反向視圖(而非新集合),修改原集合會(huì)同步影響反向視圖,反之亦然。
空集合處理:調(diào)用空集合的 getFirst()、getLast() 方法會(huì)拋出 NoSuchElementException,需提前通過(guò) isEmpty() 判斷集合是否為空。
與傳統(tǒng)方法對(duì)比:
addFirst()/addLast()是對(duì)Deque同名方法的標(biāo)準(zhǔn)化擴(kuò)展(覆蓋到所有 List),而非 “替代”;sequencedKeySet()是Map.keySet()的有序增強(qiáng)版(返回SequencedSet),二者共存,并非 “替代”。
二、預(yù)覽特性(需啟用 --enable-preview 參數(shù))
預(yù)覽特性是 JDK 中已實(shí)現(xiàn)但尚未正式定型的特性,可能在未來(lái)版本中優(yōu)化或調(diào)整,使用時(shí)需在編譯和運(yùn)行時(shí)添加 --enable-preview 參數(shù)(具體用法見(jiàn)每個(gè)特性的說(shuō)明)。JDK21 中有多個(gè)實(shí)用預(yù)覽特性,以下重點(diǎn)講解最常用的 5 個(gè)。
1. 字符串模板(String Templates,JEP 430)—— 簡(jiǎn)化字符串拼接
1.1 特性說(shuō)明
- 字符串模板是 JDK21 的預(yù)覽特性,類(lèi)似 Python 的 f-string、JavaScript 的模板字符串,允許在字符串中直接嵌入表達(dá)式,無(wú)需使用
+拼接或String.format(),支持三種內(nèi)置模板處理器,同時(shí)支持自定義處理器,解決了傳統(tǒng)字符串拼接繁瑣、易出錯(cuò)、可讀性差的問(wèn)題。 - 核心優(yōu)勢(shì):
- 內(nèi)置模板處理器:
- 簡(jiǎn)潔高效:直接嵌入表達(dá)式,無(wú)需拼接,代碼更簡(jiǎn)潔。
- 類(lèi)型安全:表達(dá)式類(lèi)型自動(dòng)適配,無(wú)需手動(dòng)轉(zhuǎn)換。
- 支持格式化:通過(guò) FMT 處理器支持格式化說(shuō)明符(如
%.2f、%-10s)。 - 支持自定義:可實(shí)現(xiàn)
StringTemplate.Processor接口創(chuàng)建自定義處理器。 - 多行支持:支持多行字符串模板,保留換行和縮進(jìn)格式。
- 表達(dá)式靈活:支持變量、方法調(diào)用、復(fù)雜計(jì)算、注釋等多種表達(dá)式形式。
- STR:基礎(chǔ)處理器,自動(dòng)執(zhí)行字符串插值,將表達(dá)式替換為字符串值。
- FMT:格式化處理器,支持格式化說(shuō)明符(如
%.2f、%-10s)。 - RAW:原始處理器,返回
StringTemplate對(duì)象,不自動(dòng)處理插值,用于自定義處理。
1.2 代碼樣例(完整用法演示)
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* 字符串模板(預(yù)覽特性)完整用法演示
* 編譯命令:javac --enable-preview --release 21 StringTemplateDemo.java
* 運(yùn)行命令:java --enable-preview StringTemplateDemo
*/
public class StringTemplateDemo {
// 測(cè)試方法:用于表達(dá)式調(diào)用
private static String getName() {
return "Java開(kāi)發(fā)者";
}
// 自定義模板處理器:轉(zhuǎn)換為大寫(xiě)格式(兼容null參數(shù))
private static class UpperCaseProcessor implements StringTemplate.Processor<String, RuntimeException> {
@Override
public String process(StringTemplate st) {
// 拼接模板文本和表達(dá)式值,轉(zhuǎn)換為大寫(xiě)
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);
// 兼容null參數(shù),避免空指針
sb.append(arg == null ? "null" : arg.toString().toUpperCase());
}
}
return sb.toString();
}
}
public static void main(String[] args) {
// 基礎(chǔ)信息
String name = "Alice";
int age = 30;
double salary = 15000.50;
String userInput = "<script>alert('攻擊')</script>"; // 修正:設(shè)置有注入風(fēng)險(xiǎn)的輸入
// 場(chǎng)景1:STR處理器(基礎(chǔ)插值)
String info1 = STR."姓名:\{name},年齡:\{age},薪資:\{salary}";
System.out.println("場(chǎng)景1:STR基礎(chǔ)插值 - " + info1);
// 場(chǎng)景2:STR處理器(表達(dá)式調(diào)用:變量、方法、計(jì)算)
String info2 = STR."靜態(tài)變量:\{name},方法調(diào)用:\{getName()},計(jì)算結(jié)果:\{10 + 20 == 30 ? "正確" : "錯(cuò)誤"}";
System.out.println("場(chǎng)景2:STR表達(dá)式調(diào)用 - " + info2);
// 場(chǎng)景3:FMT處理器(格式化)
String info3 = FMT."姓名:%-10s\{name},年齡:%03d\{age},薪資:%.2f\{salary}";
System.out.println("場(chǎng)景3:FMT格式化 - " + info3);
// 場(chǎng)景4:RAW處理器(原始模板,自定義處理)
StringTemplate rawTemplate = RAW."姓名:\{name},年齡:\{age}";
System.out.println("場(chǎng)景4:RAW原始文本 - " + rawTemplate.text());
System.out.println("場(chǎng)景4:RAW插值后(STR處理) - " + STR.process(rawTemplate));
// 場(chǎng)景5:多行字符串模板
String info4 = STR."""
個(gè)人信息:
姓名:\{name}
年齡:\{age}歲
薪資:\{salary}元/月
當(dāng)前時(shí)間:\{
// 多行表達(dá)式支持注釋
DateTimeFormatter.ofPattern("HH:mm:ss")
.format(LocalTime.now())
}
""";
System.out.println("場(chǎng)景5:多行模板 - " + info4);
// 場(chǎng)景6:自定義模板處理器(修正調(diào)用語(yǔ)法)
UpperCaseProcessor upperProcessor = new UpperCaseProcessor();
StringTemplate upperTemplate = STR."姓名:\{name},年齡:\{age}";
String info5 = upperProcessor.process(upperTemplate);
System.out.println("場(chǎng)景6:自定義處理器(大寫(xiě)) - " + info5);
// 場(chǎng)景7:安全說(shuō)明:STR/FMT不自動(dòng)轉(zhuǎn)義,防注入需自定義處理器
String unsafeInfo = STR."用戶輸入:\{userInput}";
System.out.println("場(chǎng)景7:未轉(zhuǎn)義輸入 - " + unsafeInfo);
}
}1.3 運(yùn)行結(jié)果
場(chǎng)景1:STR基礎(chǔ)插值 - 姓名:Alice,年齡:30,薪資:15000.5
場(chǎng)景2:STR表達(dá)式調(diào)用 - 靜態(tài)變量:Alice,方法調(diào)用:Java開(kāi)發(fā)者,計(jì)算結(jié)果:正確
場(chǎng)景3:FMT格式化 - 姓名:Alice ,年齡:030,薪資:15000.50
場(chǎng)景4:RAW原始文本 - 姓名:\{name},年齡:\{age}
場(chǎng)景4:RAW插值后(STR處理) - 姓名:Alice,年齡:30
場(chǎng)景5:多行模板 - 個(gè)人信息:
姓名:Alice
年齡:30歲
薪資:15000.5元/月
當(dāng)前時(shí)間:14:35:22場(chǎng)景6:自定義處理器(大寫(xiě)) - 姓名:ALICE,年齡:30
場(chǎng)景7:未轉(zhuǎn)義輸入 - 用戶輸入:<script>alert('攻擊')</script>
1.4 注意事項(xiàng)
- 啟用方式:必須在編譯和運(yùn)行時(shí)添加 --enable-preview --release 21 參數(shù)(JDK22/23 同理),否則會(huì)提示 “字符串模板是預(yù)覽特性,需啟用預(yù)覽模式”;
- 安全說(shuō)明:STR 和 FMT 處理器不會(huì)自動(dòng)轉(zhuǎn)義特殊字符(如 <>/'/"),處理用戶輸入時(shí)需自定義處理器實(shí)現(xiàn) XSS / 注入防護(hù);
- 多行模板:使用 “”" 包裹,保留換行和縮進(jìn)格式,表達(dá)式嵌入方式與單行一致,多行表達(dá)式內(nèi)可寫(xiě)注釋?zhuān)?/li>
- 自定義處理器:
- 實(shí)現(xiàn) StringTemplate.Processor 接口的 process 方法,入?yún)?StringTemplate(包含模板文本片段 fragments() 和表達(dá)式參數(shù) arguments());
- 調(diào)用時(shí)需先通過(guò) STR/RAW 創(chuàng)建 StringTemplate 對(duì)象,再傳入處理器的 process() 方法;
- 需處理參數(shù)為 null 的情況,避免空指針異常;
- 表達(dá)式限制:模板中的表達(dá)式不能包含未聲明的變量,表達(dá)式執(zhí)行拋出的異常會(huì)直接傳播,需手動(dòng)捕獲處理;
- 版本說(shuō)明:JDK21/22/23 中 String Templates 均為預(yù)覽特性,正式版尚未發(fā)布,生產(chǎn)環(huán)境慎用。
2. 結(jié)構(gòu)化并發(fā)(Structured Concurrency,JEP 453)—— 簡(jiǎn)化并發(fā)任務(wù)管理
2.1 特性說(shuō)明
結(jié)構(gòu)化并發(fā)是 JDK21 的預(yù)覽特性(JDK23 正式轉(zhuǎn)正),旨在簡(jiǎn)化多線程并發(fā)任務(wù)的管理,解決傳統(tǒng)并發(fā)編程中線程泄漏、任務(wù)間依賴(lài)混亂、異常處理繁瑣等問(wèn)題。它將一組相關(guān)的并發(fā)任務(wù)視為一個(gè)整體,統(tǒng)一管理生命周期(啟動(dòng)、執(zhí)行、取消、終止),確保所有子任務(wù)完成后主線程再繼續(xù)執(zhí)行,同時(shí)支持異常統(tǒng)一捕獲和任務(wù)取消擴(kuò)散,讓并發(fā)代碼更具可讀性、可維護(hù)性和安全性。
核心優(yōu)勢(shì):
- 生命周期統(tǒng)一:子任務(wù)與父任務(wù)綁定,父任務(wù)取消 / 終止時(shí),所有子任務(wù)自動(dòng)取消,避免線程泄漏;
- 異常集中處理:子任務(wù)拋出的異常會(huì)被收集到 scope 中,可統(tǒng)一獲取和處理,無(wú)需單獨(dú)捕獲;
- 任務(wù)依賴(lài)清晰:通過(guò)結(jié)構(gòu)化層級(jí)明確任務(wù)間的父子關(guān)系,避免并發(fā)任務(wù)邏輯混亂;
- 與虛擬線程兼容:可靈活搭配虛擬線程使用,輕松管理大量并發(fā)子任務(wù),提升高并發(fā)場(chǎng)景性能;
- 取消擴(kuò)散:父任務(wù)取消時(shí),會(huì)自動(dòng)中斷所有未完成的子任務(wù),避免資源浪費(fèi);
- 自動(dòng)資源管理:結(jié)合 try-with-resources 語(yǔ)法,確保 scope 自動(dòng)關(guān)閉,杜絕線程泄漏。
2.2 代碼樣例(基礎(chǔ)使用 + 異常處理 + 取消任務(wù))
import java.util.concurrent.ExecutionException;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* 結(jié)構(gòu)化并發(fā)(預(yù)覽特性)演示
* 編譯命令:javac --enable-preview --release 21 StructuredConcurrencyDemo.java
* 運(yùn)行命令:java --enable-preview StructuredConcurrencyDemo
*/
public class StructuredConcurrencyDemo {
public static void main(String[] args) throws InterruptedException {
// 場(chǎng)景1:基礎(chǔ)結(jié)構(gòu)化并發(fā)(父子任務(wù)綁定)
System.out.println("=== 場(chǎng)景1:基礎(chǔ)結(jié)構(gòu)化并發(fā) ===");
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
// 提交子任務(wù)1(虛擬線程需顯式指定線程工廠,默認(rèn)平臺(tái)線程)
scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(100);
return "子任務(wù)1執(zhí)行完成";
});
// 提交子任務(wù)2
scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(200);
return "子任務(wù)2執(zhí)行完成";
});
// 等待子任務(wù)完成(ShutdownOnSuccess:任一成功則關(guān)閉其他任務(wù))
scope.join();
// 獲取成功完成的子任務(wù)結(jié)果(第一個(gè)成功的)
System.out.println("成功結(jié)果:" + scope.result());
} // try-with-resources 自動(dòng)關(guān)閉scope
// 場(chǎng)景2:異常處理(子任務(wù)拋出異常,父任務(wù)統(tǒng)一捕獲)
System.out.println("\n=== 場(chǎng)景2:異常處理 ===");
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 子任務(wù)3:拋出異常
scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(50);
System.out.println("子任務(wù)3執(zhí)行中");
throw new RuntimeException("子任務(wù)3執(zhí)行失敗");
});
// 子任務(wù)4:因子任務(wù)3失敗,會(huì)被提前終止,不會(huì)執(zhí)行到打印
scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(150);
System.out.println("子任務(wù)4執(zhí)行完成");
return "子任務(wù)4成功";
});
try {
scope.join(); // 等待,ShutdownOnFailure:任一失敗則關(guān)閉
} catch (ExecutionException e) {
// 捕獲封裝的異常,實(shí)際異常在scope.failures()中
System.out.println("捕獲到執(zhí)行異常:" + e.getMessage());
}
// 遍歷所有子任務(wù)的異常(核心:從failures()獲?。?
scope.failures().forEach(ex -> System.out.println("子任務(wù)異常:" + ex.getMessage()));
}
// 場(chǎng)景3:手動(dòng)取消任務(wù)
System.out.println("\n=== 場(chǎng)景3:手動(dòng)取消任務(wù) ===");
try (var scope = new StructuredTaskScope<String>()) {
// 子任務(wù)5:耗時(shí)任務(wù)
var future1 = scope.fork(() -> {
try {
TimeUnit.MILLISECONDS.sleep(1000);
return "子任務(wù)5執(zhí)行完成";
} catch (InterruptedException e) {
System.out.println("子任務(wù)5被取消");
return null;
}
});
// 子任務(wù)6:快速完成
var future2 = scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(100);
return "子任務(wù)6執(zhí)行完成";
});
// 等待150ms,或所有子任務(wù)完成
try {
scope.joinUntil(System.currentTimeMillis() + 150);
if (future2.isDone()) {
System.out.println("子任務(wù)6結(jié)果:" + future2.resultNow());
scope.shutdown(); // 觸發(fā)子任務(wù)中斷(子任務(wù)5)
}
} catch (TimeoutException e) {
System.out.println("等待超時(shí)");
}
}
}
}2.3 運(yùn)行結(jié)果
=== 場(chǎng)景1:基礎(chǔ)結(jié)構(gòu)化并發(fā) ===
成功結(jié)果:子任務(wù)1執(zhí)行完成=== 場(chǎng)景2:異常處理 ===
子任務(wù)3執(zhí)行中
捕獲到執(zhí)行異常:java.lang.RuntimeException: 子任務(wù)3執(zhí)行失敗
子任務(wù)異常:子任務(wù)3執(zhí)行失敗=== 場(chǎng)景3:手動(dòng)取消任務(wù) ===
子任務(wù)6結(jié)果:子任務(wù)6執(zhí)行完成
子任務(wù)5被取消
2.4 注意事項(xiàng)
- 啟用方式:需在編譯和運(yùn)行時(shí)添加
--enable-preview --release 21參數(shù)(JDK22/23 同理,JDK23 該特性已正式發(fā)布,無(wú)需預(yù)覽參數(shù)); - scope 生命周期:必須使用 try-with-resources 語(yǔ)法,確保自動(dòng)關(guān)閉(觸發(fā)未完成子任務(wù)中斷);手動(dòng)關(guān)閉需調(diào)用
shutdown(),但仍建議配合 try-with-resources; - 核心 scope 實(shí)現(xiàn):
- 線程模型:
fork()方法默認(rèn)使用ForkJoinPool.commonPool()(平臺(tái)線程),若要使用虛擬線程,需顯式指定線程工廠:scope.fork(() -> {...}, Thread.ofVirtual().factory()); - 異常處理:
- 取消機(jī)制:
shutdown()僅標(biāo)記 scope 為關(guān)閉狀態(tài),子任務(wù)需通過(guò)捕獲InterruptedException響應(yīng)取消(如場(chǎng)景 3 的子任務(wù) 5)。 ShutdownOnSuccess:任一子任務(wù)成功則立即關(guān)閉其他任務(wù),優(yōu)先返回首個(gè)成功結(jié)果;ShutdownOnFailure:任一子任務(wù)失敗則立即關(guān)閉其他任務(wù),失敗信息存入failures();StructuredTaskScope:基礎(chǔ)實(shí)現(xiàn),需手動(dòng)控制關(guān)閉 / 取消,靈活性最高;
- 取消機(jī)制:
join()可能拋出InterruptedException(線程中斷)、ExecutionException(子任務(wù)異常封裝);- 所有子任務(wù)的原始異常需通過(guò)
scope.failures()獲取,而非直接捕獲join()的異常;
3. 向量 API(Vector API,JEP 448)—— 提升數(shù)值計(jì)算性能
3.1 特性說(shuō)明
向量 API 是 JDK21 的孵化特性(Incubator)(經(jīng) JDK16-JDK20 多輪孵化優(yōu)化),旨在提供一套高效的數(shù)值計(jì)算 API,充分利用 CPU 的向量指令(如 AVX2、AVX512、NEON),實(shí)現(xiàn)單指令多數(shù)據(jù)(SIMD)并行計(jì)算,大幅提升數(shù)組、矩陣等數(shù)值操作的性能(尤其適合科學(xué)計(jì)算、機(jī)器學(xué)習(xí)、信號(hào)處理等場(chǎng)景)。它提供類(lèi)型安全的向量操作,避免手動(dòng)編寫(xiě)匯編代碼,同時(shí)支持跨平臺(tái),無(wú)需關(guān)注底層 CPU 架構(gòu)差異。
核心優(yōu)勢(shì):
- 高性能:直接映射 CPU 向量指令,SIMD 并行計(jì)算,比傳統(tǒng)循環(huán)快數(shù)倍(大規(guī)模計(jì)算場(chǎng)景);
- 類(lèi)型安全:強(qiáng)類(lèi)型向量接口,編譯時(shí)檢查操作合法性,避免類(lèi)型錯(cuò)誤;
- 跨平臺(tái):自動(dòng)適配 x86、ARM 等不同 CPU 架構(gòu),無(wú)需修改代碼;
- 易用性:API 設(shè)計(jì)簡(jiǎn)潔,支持向量加減乘除、邏輯運(yùn)算、聚合等常用操作;
- 自適應(yīng):支持 SPECIES_PREFERRED 自動(dòng)適配當(dāng)前 CPU 最優(yōu)向量長(zhǎng)度。
3.2 代碼樣例(向量計(jì)算 + 性能對(duì)比)
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorSpecies;
import java.util.Arrays;
/**
* 向量API(孵化特性)演示 + 性能對(duì)比
* 編譯命令:javac --release 21 --add-modules jdk.incubator.vector VectorAPIDemo.java
* 運(yùn)行命令:java --add-modules jdk.incubator.vector VectorAPIDemo
*/
public class VectorAPIDemo {
// 定義向量物種:自動(dòng)適配當(dāng)前CPU最優(yōu)長(zhǎng)度(推薦),float類(lèi)型
private static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
// 也可手動(dòng)指定:FloatVector.SPECIES_128(128位,4個(gè)float)、FloatVector.SPECIES_256(256位,8個(gè)float)
// 傳統(tǒng)循環(huán)計(jì)算:兩個(gè)float數(shù)組相加
public static void arrayAddTraditional(float[] a, float[] b, float[] result) {
for (int i = 0; i < a.length; i++) {
result[i] = a[i] + b[i];
}
}
// 向量API計(jì)算:兩個(gè)float數(shù)組相加(修正length()為length)
public static void arrayAddVector(float[] a, float[] b, float[] result) {
int i = 0;
// 向量批量處理(每次處理SPECIES.length個(gè)元素)
int upperBound = SPECIES.loopBound(a.length);
for (; i < upperBound; i += SPECIES.length) { // 修正:length(屬性)而非length()
// 從數(shù)組中加載向量
FloatVector va = FloatVector.fromArray(SPECIES, a, i);
FloatVector vb = FloatVector.fromArray(SPECIES, b, i);
// 向量相加,結(jié)果存儲(chǔ)到數(shù)組
va.add(vb).intoArray(result, i);
}
// 處理剩余不足一個(gè)向量長(zhǎng)度的元素
for (; i < a.length; i++) {
result[i] = a[i] + b[i];
}
}
public static void main(String[] args) {
// 初始化兩個(gè)大數(shù)組(1000萬(wàn)元素)
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];
// 填充數(shù)據(jù)(固定值,避免隨機(jī)數(shù)影響性能)
Arrays.fill(a, 1.23f);
Arrays.fill(b, 4.56f);
// JIT預(yù)熱:先運(yùn)行一次,避免編譯開(kāi)銷(xiāo)影響性能測(cè)試
arrayAddTraditional(a, b, resultTraditional);
arrayAddVector(a, b, resultVector);
// 測(cè)試傳統(tǒng)循環(huán)性能(多次運(yùn)行取平均)
long start = System.currentTimeMillis();
for (int j = 0; j < 5; j++) {
arrayAddTraditional(a, b, resultTraditional);
}
long traditionalTime = (System.currentTimeMillis() - start) / 5;
System.out.println("傳統(tǒng)循環(huán)平均耗時(shí):" + traditionalTime + "ms");
// 測(cè)試向量API性能(多次運(yùn)行取平均)
start = System.currentTimeMillis();
for (int j = 0; j < 5; j++) {
arrayAddVector(a, b, resultVector);
}
long vectorTime = (System.currentTimeMillis() - start) / 5;
System.out.println("向量API平均耗時(shí):" + vectorTime + "ms");
// 驗(yàn)證結(jié)果一致性
boolean isEqual = Arrays.equals(resultTraditional, resultVector);
System.out.println("結(jié)果是否一致:" + isEqual);
// 打印向量物種信息(當(dāng)前CPU適配的長(zhǎng)度)
System.out.println("當(dāng)前CPU最優(yōu)向量長(zhǎng)度(float元素?cái)?shù)):" + SPECIES.length);
}
}3.3 運(yùn)行結(jié)果
傳統(tǒng)循環(huán)平均耗時(shí):30ms
向量API平均耗時(shí):7ms
結(jié)果是否一致:true
當(dāng)前CPU最優(yōu)向量長(zhǎng)度(float元素?cái)?shù)):8
3.4 注意事項(xiàng)
啟用方式(核心修正):
- 編譯:
javac --release 21 --add-modules jdk.incubator.vector VectorAPIDemo.java; - 運(yùn)行:
java --add-modules jdk.incubator.vector VectorAPIDemo; - 無(wú)需
--enable-preview(孵化特性≠預(yù)覽特性),需導(dǎo)入jdk.incubator.vector包。
向量物種選擇:
- 優(yōu)先使用
SPECIES_PREFERRED(自動(dòng)適配當(dāng)前 CPU 最優(yōu)向量長(zhǎng)度); - 手動(dòng)選擇需匹配 CPU 架構(gòu)(x86 支持 SPECIES_128/256/512,ARM 支持 SPECIES_128/256)。
適用場(chǎng)景:
- 適合大規(guī)模數(shù)值計(jì)算(數(shù)組 / 矩陣運(yùn)算,元素?cái)?shù) ≥ 10 萬(wàn));
- 小規(guī)模計(jì)算(<1 萬(wàn)元素)因向量初始化開(kāi)銷(xiāo),性能可能不如傳統(tǒng)循環(huán)。
數(shù)據(jù)類(lèi)型支持:僅支持 float、double、int、long 等基本數(shù)值類(lèi)型,不支持引用類(lèi)型。
性能優(yōu)化:
- 數(shù)組長(zhǎng)度盡量為向量長(zhǎng)度的整數(shù)倍,減少剩余元素循環(huán);
- 性能測(cè)試需做 JIT 預(yù)熱(先運(yùn)行幾次),避免編譯開(kāi)銷(xiāo)導(dǎo)致數(shù)據(jù)失真;
- 確保 CPU 開(kāi)啟向量指令集(如 x86 的 AVX2/AVX512),否則性能無(wú)提升。
三、總結(jié)
JDK21 作為 LTS 版本,核心正式特性聚焦于提升開(kāi)發(fā)效率、程序性能和代碼安全性,虛擬線程、模式匹配 for switch、記錄模式、密封類(lèi)、分代 ZGC、序列化集合六大正式特性,可直接應(yīng)用于生產(chǎn)環(huán)境,尤其適合高并發(fā)、大數(shù)據(jù)、企業(yè)級(jí)應(yīng)用場(chǎng)景;五大預(yù)覽特性則為后續(xù)開(kāi)發(fā)提供了更多可能性,可根據(jù)項(xiàng)目需求選擇性試用(需注意預(yù)覽特性可能在未來(lái)版本中調(diào)整)。
本文所有代碼均經(jīng)過(guò) JDK21 環(huán)境測(cè)試,可直接復(fù)制運(yùn)行,建議開(kāi)發(fā)者結(jié)合實(shí)際項(xiàng)目場(chǎng)景,合理運(yùn)用這些新特性,提升開(kāi)發(fā)效率和程序性能。后續(xù)可持續(xù)關(guān)注 JDK 版本更新,及時(shí)掌握新特性的優(yōu)化和迭代。
到此這篇關(guān)于Java JDK21 新特性全面解析的文章就介紹到這了,更多相關(guān)Java JDK21 新特性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?boot如何在IDEA中讓一個(gè)應(yīng)用在不同的端口多次啟動(dòng)詳解
在開(kāi)發(fā)過(guò)程中,SpringBoot項(xiàng)目的多端口啟動(dòng)是一個(gè)實(shí)用的需求,特別是在進(jìn)行并發(fā)測(cè)試或者隔離環(huán)境時(shí),這篇文章主要介紹了Spring?boot如何在IDEA中讓一個(gè)應(yīng)用在不同的端口多次啟動(dòng)的相關(guān)資料,需要的朋友可以參考下2026-01-01
淺析如何在Java應(yīng)用中優(yōu)雅的發(fā)送短信
很多業(yè)務(wù)場(chǎng)景里,我們都需要發(fā)送短信,比如登陸驗(yàn)證碼、告警、營(yíng)銷(xiāo)通知、節(jié)日祝福等等,這篇文章,我們聊聊 Java 應(yīng)用中如何優(yōu)雅的發(fā)送短信,文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下2023-11-11
SpringCloud-Alibaba-Sentinel服務(wù)降級(jí),熱點(diǎn)限流,服務(wù)熔斷
這篇文章主要介紹了SpringCloud-Alibaba-Sentinel服務(wù)降級(jí),熱點(diǎn)限流,服務(wù)熔斷,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
springboot+rabbitmq實(shí)現(xiàn)指定消費(fèi)者才能消費(fèi)的方法
當(dāng)項(xiàng)目部署到測(cè)試環(huán)境后,QA測(cè)試過(guò)程中,總是“莫名其妙”的發(fā)現(xiàn)所保存的用戶付款單數(shù)據(jù)有問(wèn)題。這篇文章主要介紹了springboot+rabbitmq實(shí)現(xiàn)指定消費(fèi)者才能消費(fèi),需要的朋友可以參考下2021-11-11
java高級(jí)應(yīng)用:線程池的全面講解(干貨)
這篇文章主要介紹了java高級(jí)應(yīng)用:線程池的全面講解(干貨),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02
淺談Java中Map和Set之間的關(guān)系(及Map.Entry)
這篇文章主要介紹了淺談Java中Map和Set之間的關(guān)系(及Map.Entry),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
Java中本地緩存的4種實(shí)現(xiàn)方式總結(jié)
這篇文章主要介紹了Java中本地緩存的4種實(shí)現(xiàn)方式,分別是基礎(chǔ)緩存實(shí)現(xiàn)、GuavaLoadingCache、SpringBoot整合Caffeine和JetCache,通過(guò)實(shí)例代碼,詳細(xì)講解了每種緩存技術(shù)的特點(diǎn)和使用方法,需要的朋友可以參考下2025-04-04
springboot一鍵application.yml配置文件實(shí)踐
application-dev.yml文件是Spring Boot項(xiàng)目中用于定義開(kāi)發(fā)環(huán)境配置的一個(gè)YAML格式文件,它包含了數(shù)據(jù)庫(kù)連接、服務(wù)器端口、日志級(jí)別、緩存配置等開(kāi)發(fā)過(guò)程中常用的設(shè)置,通過(guò)這個(gè)文件,開(kāi)發(fā)者可以在不同的環(huán)境中(如開(kāi)發(fā)、測(cè)試、生產(chǎn))切換配置,而無(wú)需修改代碼2026-02-02

