Java 17 新特性解析與代碼示例
文章目錄
- Java 17 新特性解析與代碼示例
- 引言
- 1. 密封類(JEP 409)
- 1.1. 介紹
- 1.2. 詳細說明
- 1.3. 代碼示例
- 1.4. 與之前功能的對比
- 1.5. 使用場景
- 1.6. 總結
- 2. switch 模式匹配(預覽,JEP 406)
- 2.1. 介紹
- 2.2. 詳細說明
- 2.3. 代碼示例
- 2.4. 與之前功能的對比
- 2.5. 使用場景
- 2.6. 總結
- 3. 外部函數和內存 API(孵化,JEP 412)
- 3.1. 介紹
- 3.2. 詳細說明
- 3.3. 代碼示例
- 3.4. 與之前功能的對比
- 3.5. 使用場景
- 3.6. 總結
- 4. 增強的偽隨機數生成器(JEP 356)
- 4.1. 介紹
- 4.2. 詳細說明
- 4.3. 代碼示例
- 4.4. 與之前功能的對比
- 4.5. 使用場景
- 4.6. 總結
- 5. 上下文特定的反序列化過濾器(JEP 415)
- 5.1. 介紹
- 5.2. 詳細說明
- 5.3. 代碼示例
- 5.4. 與之前功能的對比
- 5.5. 使用場景
- 5.6. 總結
- 6. JDK 內部的強封裝(JEP 403)
- 6.1. 介紹
- 6.2. 詳細說明
- 6.3. 影響與應對
- 6.4. 使用場景
- 6.5. 總結
- 7. 安全管理器的棄用(JEP 411)
- 7.1. 介紹
- 7.2. 詳細說明
- 7.3. 影響與應對
- 7.4. 使用場景
- 7.5. 總結
- 8. 其他值得注意的特性
- 8.1. 新 macOS 渲染管道(JEP 382)
- 8.2. macOS/AArch64 支持(JEP 391)
- 8.3. 十六進制格式化工具
- 8.4. 總結
- 9. 結論
引言
Java 17 是 Java SE 平臺的最新長期支持(LTS)版本,于 2021 年 9 月 15 日發布。作為繼 Java 11 之后的又一個 LTS 版本,Java 17 引入了多項新功能和改進,旨在提升開發者生產力、增強程序性能并提高安全性。根據 Oracle 的支持路線圖,Java 17 將獲得至少八年的支持,使其成為企業級應用的理想選擇。
本文將深入探討 Java 17 的主要新特性,包括密封類、switch 模式匹配(預覽)、外部函數和內存 API(孵化)、增強的偽隨機數生成器以及上下文特定的反序列化過濾器。每項特性都將配有詳細的代碼示例,對于優化現有功能的特性,我們將提供對比代碼以展示改進。此外,我們還將簡要介紹其他值得注意的變化,如 JDK 內部的強封裝和安全管理器的棄用。
通過閱讀本文,開發者將能夠全面了解 Java 17 的新特性,并通過實際代碼示例掌握如何在項目中應用這些特性。所有信息均基于 Oracle 官方文檔和其他權威資源,確保準確性和可靠性。
1. 密封類(JEP 409)
1.1. 介紹
密封類是 Java 17 中引入的一項重要特性,允許開發者限制哪些類或接口可以擴展或實現某個類或接口。這一特性在 JDK 15 和 16 中作為預覽功能引入,并在 Java 17 中正式標準化。密封類增強了繼承控制,有助于設計更安全、更可維護的代碼。
1.2. 詳細說明
在傳統 Java 中,類可以被任意類擴展,除非標記為 final
。然而,在某些場景下,開發者希望只允許特定的子類擴展基類。例如,在圖形應用中,可能希望 Shape
類只能被 Circle
、Rectangle
和 Triangle
擴展。密封類通過 sealed
關鍵字和 permits
子句實現這一目標。
密封類的子類必須標記為 final
(不可再擴展)、sealed
(可以指定自己的子類)或 non-sealed
(允許任意擴展)。這一機制不僅提高了代碼的安全性,還在模式匹配(如 switch 表達式)中提供了編譯時檢查,確保所有可能的情況都被處理。
1.3. 代碼示例
以下是一個使用密封類定義圖形層次結構的示例:
public sealed class Shape permits Circle, Rectangle, Triangle {public abstract double area();
}public final class Circle extends Shape {private final double radius;public Circle(double radius) {this.radius = radius;}@Overridepublic double area() {return Math.PI * radius * radius;}
}public final class Rectangle extends Shape {private final double width, height;public Rectangle(double width, double height) {this.width = width;this.height = height;}@Overridepublic double area() {return width * height;}
}public final class Triangle extends Shape {private final double base, height;public Triangle(double base, double height) {this.base = base;this.height = height;}@Overridepublic double area() {return 0.5 * base * height;}
}
在這個例子中,Shape
是一個密封類,只允許 Circle
、Rectangle
和 Triangle
擴展它。每個子類都被標記為 final
,表示它們不能被進一步擴展。
由于 Shape
是密封類,我們可以在 switch 表達式中利用其封閉性,確保覆蓋所有可能的情況,而無需默認分支:
public class ShapeDemo {public static String describe(Shape shape) {return switch (shape) {case Circle c -> "圓形,面積:" + c.area();case Rectangle r -> "矩形,面積:" + r.area();case Triangle t -> "三角形,面積:" + t.area();};}public static void main(String[] args) {Shape circle = new Circle(5.0);Shape rectangle = new Rectangle(4.0, 6.0);Shape triangle = new Triangle(3.0, 8.0);System.out.println(describe(circle));System.out.println(describe(rectangle));System.out.println(describe(triangle));}
}
輸出:
圓形,面積:78.53981633974483
矩形,面積:24.0
三角形,面積:12.0
如果遺漏了某個子類,編譯器會報錯,確保代碼的完整性。
1.4. 與之前功能的對比
在 Java 17 之前,控制繼承通常需要使用 final
關鍵字或包私有類,但這些方法缺乏靈活性。例如,final
完全禁止擴展,而包私有類限制了訪問范圍。密封類提供了一種中間方案,允許開發者明確指定允許的子類,同時保持代碼的開放性。
傳統方式:
public abstract class Shape {// 無法限制哪些類可以擴展
}public class Circle extends Shape {// ...
}public class Rectangle extends Shape {// ...
}// 任何類都可以擴展 Shape,可能導致意外的子類
public class UnknownShape extends Shape {// ...
}
密封類通過 permits
子句解決了這個問題,確保只有指定的子類可以擴展基類。
1.5. 使用場景
- API 設計:在設計庫或框架時,限制用戶擴展特定的類。
- 領域建模:在需要固定類型集合的場景中,如代數數據類型。
- 模式匹配:與 switch 表達式結合,確保所有情況都被處理。
1.6. 總結
密封類為 Java 開發者提供了更精細的繼承控制,增強了代碼的安全性和可維護性。通過與模式匹配結合,密封類還可以提高代碼的健壯性,是 Java 17 中一項強大的新特性。
2. switch 模式匹配(預覽,JEP 406)
2.1. 介紹
switch 模式匹配是 Java 17 中的一項預覽特性,增強了 switch 語句和表達式的表達能力。它允許在 case 標簽中使用模式匹配,從而簡化類型檢查和轉換的代碼。這一特性在 JDK 14 和 16 中逐步引入,并在 Java 17 中進一步完善。
2.2. 詳細說明
傳統 switch 語句需要顯式的類型檢查和轉換,通常導致冗長的代碼。switch 模式匹配允許直接在 case 標簽中指定類型模式,自動完成類型檢查和轉換。此外,還支持守衛模式(guarded patterns),允許在模式中添加條件。
由于這是預覽特性,使用時需要啟用 --enable-preview
標志:
javac --enable-preview --release 17 MyClass.java
java --enable-preview MyClass
2.3. 代碼示例
以下是一個使用 switch 模式匹配的示例:
public class PatternMatchingDemo {public static void process(Object obj) {switch (obj) {case String s when s.length() > 5 -> System.out.println("長字符串:" + s);case String s -> System.out.println("短字符串:" + s);case Integer i -> System.out.println("整數:" + i * 2);default -> System.out.println("未知類型");}}public static void main(String[] args) {process("Hello");process("Hello, World!");process(42);process(new Object());}
}
輸出:
短字符串:Hello
長字符串:Hello, World!
整數:84
未知類型
在這個例子中,switch 表達式根據對象的類型和條件(字符串長度)執行不同的操作。
2.4. 與之前功能的對比
在 Java 17 之前,處理類似邏輯需要使用 instanceof
和顯式轉換:
public class OldPatternMatchingDemo {public static void process(Object obj) {if (obj instanceof String s) {if (s.length() > 5) {System.out.println("長字符串:" + s);} else {System.out.println("短字符串:" + s);}} else if (obj instanceof Integer i) {System.out.println("整數:" + i * 2);} else {System.out.println("未知類型");}}
}
相比之下,switch 模式匹配更簡潔,減少了樣板代碼,提高了可讀性。
2.5. 使用場景
- 簡化條件邏輯:在需要根據類型執行不同操作的場景中。
- 數據處理管道:在處理復雜數據結構時,減少嵌套的 if-else 語句。
- 與密封類結合:確保所有類型都被處理,提高代碼健壯性。
2.6. 總結
switch 模式匹配顯著提高了 switch 語句的表達能力,使代碼更簡潔、更易讀。作為預覽特性,開發者需要注意其 API 可能在未來版本中發生變化,但它已經展示了在簡化復雜邏輯方面的巨大潛力。
3. 外部函數和內存 API(孵化,JEP 412)
3.1. 介紹
外部函數和內存 API 是 Java 17 中的一項孵化特性,旨在提供一種更安全、更高效的方式與本地代碼和內存交互。它替代了傳統的 Java 本地接口(JNI),減少了樣板代碼和潛在風險。
3.2. 詳細說明
傳統 JNI 要求編寫復雜的 C 代碼來橋接 Java 和本地庫,容易出錯且難以維護。外部函數和內存 API 提供了一種純 Java 的方式來調用本地函數和訪問堆外內存。它通過 jdk.incubator.foreign
包實現,使用時需要添加模塊:
--add-modules jdk.incubator.foreign
3.3. 代碼示例
以下是一個調用 C 標準庫函數 strlen
的示例:
import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.SymbolLookup;
import java.lang.invoke.MethodHandle;public class ForeignFunctionDemo {public static void main(String[] args) {SymbolLookup stdlib = CLinker.systemLookup();MemoryAddress strlenAddr = stdlib.lookup("strlen").orElseThrow();FunctionDescriptor fd = FunctionDescriptor.of(CLinker.C_LONG, CLinker.C_POINTER);MethodHandle strlen = CLinker.getInstance().downcallHandle(strlenAddr, fd);try (MemorySegment str = CLinker.toCString("Hello, World!")) {long length = (long) strlen.invokeExact(str.address());System.out.println("字符串長度:" + length);} catch (Throwable e) {e.printStackTrace();}}
}
輸出:
字符串長度:13
這個示例展示了如何使用 API 調用 C 的 strlen
函數計算字符串長度。
3.4. 與之前功能的對比
在 Java 17 之前,調用 strlen
需要編寫 JNI 代碼,包括 C 文件和 Java 本地方法聲明,復雜且容易出錯。外部函數和內存 API 消除了這些復雜性,提供了更簡潔的接口。
3.5. 使用場景
- 性能關鍵操作:與本地庫交互以實現高性能計算。
- 硬件訪問:直接操作硬件資源或堆外內存。
- 遺留系統集成:與現有的 C/C++ 庫集成。
3.6. 總結
外部函數和內存 API 為 Java 與本地代碼的交互提供了現代化的解決方案。盡管目前是孵化狀態,但它展示了未來替代 JNI 的潛力。開發者在使用時需注意其實驗性質。
4. 增強的偽隨機數生成器(JEP 356)
4.1. 介紹
Java 17 引入了新的偽隨機數生成器(PRNG)接口和實現,提供了更靈活、更高效的隨機數生成方式。這一特性通過 java.util.random.RandomGenerator
接口實現,支持多種算法。
4.2. 詳細說明
傳統 java.util.Random
類功能有限,且在并行場景下性能不佳。Java 17 的新 PRNG API 引入了 RandomGenerator
接口,支持多種算法(如 LXM、SplittableRandom),并提供了跳躍(jumpable)和分裂(splittable)功能,適合并行處理。
4.3. 代碼示例
以下是使用新 PRNG 的示例:
import java.util.random.RandomGenerator;public class RandomDemo {public static void main(String[] args) {RandomGenerator rng = RandomGenerator.of("L32X64MixRandom");System.out.println("隨機整數:" + rng.nextInt());System.out.println("隨機雙精度數:" + rng.nextDouble());}
}
輸出示例:
隨機整數:123456789
隨機雙精度數:0.678912345
4.4. 與之前功能的對比
傳統方式使用 java.util.Random
:
import java.util.Random;public class OldRandomDemo {public static void main(String[] args) {Random random = new Random();System.out.println("隨機整數:" + random.nextInt());System.out.println("隨機雙精度數:" + random.nextDouble());}
}
新 API 提供了更多算法選擇,并支持并行場景。例如,SplittableRandom
適合多線程應用:
import java.util.random.RandomGenerator;
import java.util.random.RandomGenerator.SplittableGenerator;public class SplittableRandomDemo {public static void main(String[] args) {SplittableGenerator rng = RandomGenerator.of("SplittableRandom");SplittableGenerator subRng = rng.split();System.out.println("主生成器隨機數:" + rng.nextInt());System.out.println("子生成器隨機數:" + subRng.nextInt());}
}
4.5. 使用場景
- 模擬和游戲:需要高質量隨機數的場景。
- 并行處理:在多線程環境中生成獨立的隨機數流。
- 密碼學:選擇適合安全需求的算法。
4.6. 總結
增強的偽隨機數生成器提供了更靈活、更高效的隨機數生成方式,特別適合并行和性能敏感的應用。開發者可以根據需求選擇合適的算法。
5. 上下文特定的反序列化過濾器(JEP 415)
5.1. 介紹
反序列化是 Java 中一個已知的安全風險,可能導致任意代碼執行。Java 17 引入了上下文特定的反序列化過濾器,允許開發者為每次反序列化配置過濾器,以提高安全性。
5.2. 詳細說明
新特性通過 JVM 全局過濾器工廠實現,允許動態檢查和拒絕不安全的序列化對象。開發者可以定義過濾器,只允許特定類被反序列化,從而降低攻擊風險。
5.3. 代碼示例
以下是定義和使用反序列化過濾器的示例:
import java.io.ObjectInputFilter;public class DeserializationFilterDemo {public static ObjectInputFilter createFilter() {return (info) -> {if (info.serialClass() != null && info.serialClass().getName().startsWith("com.example.")) {return ObjectInputFilter.Status.ALLOWED;}return ObjectInputFilter.Status.REJECTED;};}public static void main(String[] args) {ObjectInputFilter.Config.setSerialFilter(createFilter());// 反序列化代碼}
}
這個過濾器只允許 com.example
包中的類被反序列化。
5.4. 與之前功能的對比
在 Java 17 之前,反序列化安全依賴于全局過濾器或手動檢查,配置復雜且不夠靈活。新特性提供了更細粒度的控制,簡化了安全配置。
5.5. 使用場景
- 安全敏感應用:防止反序列化攻擊。
- 遺留系統:為現有代碼添加安全層。
- 微服務:在分布式系統中保護數據傳輸。
5.6. 總結
上下文特定的反序列化過濾器顯著提高了 Java 應用的安全性,是處理不信任數據時的重要工具。
6. JDK 內部的強封裝(JEP 403)
6.1. 介紹
Java 17 通過 JEP 403 加強了 JDK 內部 API 的封裝,默認禁止訪問除關鍵 API(如 sun.misc.Unsafe
)外的內部元素,以提高安全性和可維護性。
6.2. 詳細說明
在早期 Java 版本中,開發者可以通過反射訪問 JDK 內部 API,這可能導致不穩定的代碼。Java 17 默認啟用強封裝,禁止訪問大多數內部 API,鼓勵使用標準 API。
6.3. 影響與應對
開發者需要檢查代碼是否依賴內部 API,并尋找替代方案。例如,sun.misc.Unsafe
的替代方案可能包括新的標準 API 或第三方庫。
6.4. 使用場景
- 遷移到 Java 17:檢查和更新依賴內部 API 的代碼。
- 安全增強:確保應用不依賴不穩定的內部實現。
6.5. 總結
強封裝提高了 JDK 的安全性和穩定性,開發者應盡早遷移到標準 API。
7. 安全管理器的棄用(JEP 411)
7.1. 介紹
Java 17 棄用了安全管理器(Security Manager),計劃在未來版本中移除。這一變化反映了 Java 安全模型的演變。
7.2. 詳細說明
安全管理器用于限制代碼的權限,但其復雜性和維護成本高。現代 Java 應用更傾向于使用模塊化和容器化來實現安全隔離。
7.3. 影響與應對
開發者應評估是否依賴安全管理器,并考慮使用其他安全機制,如模塊系統或操作系統級隔離。
7.4. 使用場景
- 遺留系統遷移:更新依賴安全管理器的代碼。
- 現代安全實踐:采用容器化或模塊化方案。
7.5. 總結
安全管理器的棄用標志著 Java 安全模型的現代化,開發者需要適應新的安全實踐。
8. 其他值得注意的特性
8.1. 新 macOS 渲染管道(JEP 382)
Java 17 引入了基于 Apple Metal 的新渲染管道,優化了 macOS 上的圖形性能。默認禁用,可通過 -Dsun.java2d.metal=true
啟用。
8.2. macOS/AArch64 支持(JEP 391)
Java 17 支持 macOS 上的 AArch64 架構(如 Apple M1 芯片),確保在新型 Mac 設備上的兼容性。
8.3. 十六進制格式化工具
java.util.HexFormat
類提供了便捷的十六進制轉換功能,適用于字節數組和基本類型的格式化。
import java.util.HexFormat;public class HexFormatDemo {public static void main(String[] args) {HexFormat hex = HexFormat.of().withUpperCase();byte[] bytes = {0x1A, 0x2B, 0x3C};String hexStr = hex.formatHex(bytes);System.out.println("十六進制:" + hexStr);}
}
輸出:
十六進制:1A2B3C
8.4. 總結
這些特性進一步豐富了 Java 17 的功能,提供了更好的平臺支持和實用工具。
9. 結論
Java 17 作為 LTS 版本,帶來了多項重要的新特性和改進。密封類增強了繼承控制,switch 模式匹配簡化了條件邏輯,外部函數和內存 API 提供了與本地代碼交互的新方式,增強的偽隨機數生成器提高了隨機數生成的靈活性,而上下文特定的反序列化過濾器加強了安全性。此外,JDK 內部的強封裝和安全管理器的棄用標志著 Java 平臺的現代化。
通過本文提供的詳細說明和代碼示例,開發者可以深入理解這些特性并在項目中應用它們。建議開發者嘗試這些新特性,并在遷移到 Java 17 時注意兼容性問題。更多詳細信息可參考 Oracle JDK 17 發布說明 和 Baeldung Java 17 新特性。