JDK19新特性使用實例詳解
前提
JDK19
於2022-09-20
發佈GA
版本,本文將會詳細介紹JDK19
新特性的使用。
新特性列表
新特性列表如下:
JPE-405
:Record
模式(預覽功能)JPE-422
:JDK
移植到Linux/RISC-V
JPE-424
:外部函數和內存API
(預覽功能)JPE-425
:虛擬線程,也就是協程(預覽功能)JPE-426
:向量API
(第四次孵化)JPE-427
:switch
匹配模式(第三次預覽)JPE-428
:結構化並發(孵化功能)
新特性使用詳解
下面就每個新特性介紹其使用方式。
Record模式
使用Record
模式增強Java
編程語言以解構Record
值。可以嵌套Record
模式和Type
模式,以實現強大的、聲明性的和可組合的數據導航和處理形式。這個描述看起來有點抽象,下面舉幾個JEP-405
的例子結合文字理解一下。以JDK16
擴展的instanceof
關鍵字下使用Type
模式來看:
// JDK16以前 private static void oldInstanceOf(Object x) { if (x instanceof String) { String s = (String) x; System.out.println(s); } } // JDK16或之後啟用instanceof下的Type模式 private static void newInstanceOfTypePattern(Object x) { if (x instanceof String s) { System.out.println(s); } }
Type
模式在JDK17
和JDK18
擴展到switch
預覽功能中,應用於其case
標簽:
// DEMO-1 private static void switchTypePattern(String s) { switch (s) { case null -> System.out.println("NULL"); case "Foo", "Bar" -> System.out.println("Foo or Bar"); default -> System.out.println("Default"); } } // DEMO-2 interface Shape{} class Rectangle implements Shape{} class Triangle implements Shape{ public int calculateArea(){ return 200; } } private static void switchTypePatternForShape(Shape shape) { switch (shape) { case null: break; case Rectangle r: System.out.printf("Rectangle[%s]\n", r); break; case Triangle t: if (t.calculateArea() > 100) { System.out.printf("Large triangle[%s]\n", t); } default: System.out.println("Default shape"); } } // DEMO-3 patterns in labels private static void switchTypeForLabels(Object x) { String formatted = switch (x) { case Integer i -> String.format("int => %d", i); case Long l -> String.format("long => %d", l); case Double d -> String.format("double => %f", d); case String s -> String.format("string => %s", s); default -> x.toString(); }; }
本次的Record
模式預覽功能就是基於record
關鍵字實現上面的Type
類型或者switch
模式。例如:
// DEMO-1 record Point(int x,int y){} private static void printSum(Object o){ if (o instanceof Point(int x,int y)){ System.out.println(x + y); } }
record
類中如果存在泛型參數可以進行類型轉換和推導,例如:
// DEMO-2 record Holder<T>(T target){} // 擦除後 private void convert(Holder<Object> holder){ if (Objects.nonNull(holder) && holder instanceof Holder<Object>(String target)) { System.out.printf("string => %s\n", target); } } // 非擦除 private <T> void convert(Holder<T> holder){ if (Objects.nonNull(holder) && holder instanceof Holder<T>(String target)) { System.out.printf("string => %s\n", target); } }
然後看record
和switch
結合使用:
// DEMO-3 sealed interface I permits C, D {} final class C implements I {} final class D implements I {} Second<I,I> second; private void recordSwitch() { second = new Second<>(new D(), new C()); // second = new Second<>(new C(), new D()); switch (second) { case Second<I, I>(C c,D d) -> System.out.printf("c => %s,d => %s", c, d); case Second<I, I>(D d,C c) -> System.out.printf("d => %s,c => %s", d, c); default -> System.out.println("default"); } }
這種模式比較復雜,因為涉及到record
類、switch
模式、泛型參數並且參數類型是接口,case
子句處理的時候必須覆蓋該泛型參數接口的所有子類型
不得不說,JDK引入的語法糖越來越復雜,功能看起來是強大的,但是編碼的可讀性在未適應期有所下降
Linux/RISC-V移植
通過Linux/RISC-V
移植,Java
將獲得對硬件指令集的支持,該指令集已被廣泛的語言工具鏈支持。RISC-V
是一種包含矢量指令的通用64
位 ISA
,目前該端口支持以下的HotSpot VM
選項:
- 模板解釋器
- 客戶端
JIT
編譯器 - 服務端
JIT
編譯器 - 包括
ZGC
和Shenandoah
在內的主流垃圾收集器
該移植基本已經完成,JEP
的重點是將該端口集成到JDK
的主倉庫中。
外部函數和內存API
外部函數和內存API
的主要功能是引入一組API
,Java
程序可以通過該組API
與Java
運行時之外的代碼和數據進行交互。有以下目標:
- 易用性:通過卓越的純
Java
開發模型代替JNI
- 高性能:提供能與當前
JNI
或者Unsafe
相當甚至更優的性能 - 通用性:提供支持不同種類的外部內存(如本地內存、持久化內存和托管堆內存)的
API
,並隨著時間推移支持其他操作系統甚至其他語言編寫的外部函數 - 安全性:允許程序對外部內存執行不安全的操作,但默認警告用戶此類操作
核心的API
和功能如下:
- 分配外部內存:
MemorySegment
、MemoryAddress
和SegmentAllocator
- 操作和訪問結構化的外部內存:
MemoryLayout
和VarHandle
- 控制外部內存:
MemorySession
- 調用外部函數:
Linker
、FunctionDescriptor
和SymbolLookup
這些API
統稱為FFM API
,位於java.base
模塊的java.lang.foreign
包中。由於API
比較多並且不算簡單,這裡隻舉一個簡單的例子:
public class AllocMemoryMain { public static void main(String[] args) { new AllocMemoryMain().allocMemory(); } /** * 分配內存 * struct Point { * int x; * int y; * } pts[10]; */ public void allocMemory() { Random random = new Random(); // 分配本地內存 MemorySegment segment = MemorySegment.allocateNative(2 * 4 * 10, MemorySession.openImplicit()); // 創建順序內存佈局 SequenceLayout ptsLayout = MemoryLayout.sequenceLayout(10, MemoryLayout.structLayout( ValueLayout.JAVA_INT.withName("x"), ValueLayout.JAVA_INT.withName("y"))); // 對內存設置值 VarHandle xHandle = ptsLayout.varHandle(MemoryLayout.PathElement.sequenceElement(), MemoryLayout.PathElement.groupElement("x")); VarHandle yHandle = ptsLayout.varHandle(MemoryLayout.PathElement.sequenceElement(), MemoryLayout.PathElement.groupElement("y")); for (int i = 0; i < ptsLayout.elementCount(); i++) { int x = i * random.nextInt(100); int y = i * random.nextInt(100); xHandle.set(segment,/* index */ (long) i,/* value to write */x); // x yHandle.set(segment,/* index */ (long) i,/* value to write */ y); // y System.out.printf("index => %d, x = %d, y = %d\n", i, x, y); } // 獲取內存值 int xValue = (int) xHandle.get(segment, 5); System.out.println("Point[5].x = " + xValue); int yValue = (int) yHandle.get(segment, 6); System.out.println("Point[6].y = " + yValue); } } // 某次執行輸出結果 index => 0, x = 0, y = 0 index => 1, x = 79, y = 16 index => 2, x = 164, y = 134 index => 3, x = 150, y = 60 index => 4, x = 152, y = 232 index => 5, x = 495, y = 240 index => 6, x = 54, y = 162 index => 7, x = 406, y = 644 index => 8, x = 464, y = 144 index => 9, x = 153, y = 342 Point[5].x = 495 Point[6].y = 162
FFM API
是一組極度強大的API
,有瞭它可以靈活地安全地使用外部內存和外部(跨語言)函數。
虛擬線程
虛擬線程,就是輕量級線程,也就是俗稱的協程,虛擬線程的資源分配和調度由VM
實現,與平臺線程(platform thread
)有很大的不同。從目前的源代碼來看,虛擬線程的狀態管理、任務提交、休眠和喚醒等也是完全由VM
實現。可以通過下面的方式創建虛擬線程:
// 方式一:直接啟動虛擬線程,因為默認參數原因這樣啟動的虛擬線程名稱為空字符串 Thread.startVirtualThread(() -> { Thread thread = Thread.currentThread(); System.out.printf("線程名稱:%s,是否虛擬線程:%s\n", thread.getName(), thread.isVirtual()); }); // 方式二:Builder模式構建 Thread vt = Thread.ofVirtual().allowSetThreadLocals(false) .name("VirtualWorker-", 0) .inheritInheritableThreadLocals(false) .unstarted(() -> { Thread thread = Thread.currentThread(); System.out.printf("線程名稱:%s,是否虛擬線程:%s\n", thread.getName(), thread.isVirtual()); }); vt.start(); // 方式三:Factory模式構建 ThreadFactory factory = Thread.ofVirtual().allowSetThreadLocals(false) .name("VirtualFactoryWorker-", 0) .inheritInheritableThreadLocals(false) .factory(); Thread virtualWorker = factory.newThread(() -> { Thread thread = Thread.currentThread(); System.out.printf("線程名稱:%s,是否虛擬線程:%s\n", thread.getName(), thread.isVirtual()); }); virtualWorker.start(); // 可以構建"虛擬線程池" ExecutorService executorService = Executors.newThreadPerTaskExecutor(factory);
由於虛擬線程的功能還處於預覽階段,創建協程的時候無法自定義執行器(準確來說是運載線程),目前所有虛擬線程都是交由一個內置的全局ForkJoinPool
實例執行,實現方式上和JDK8
中新增的並行流比較接近。另外,目前來看虛擬線程和原來的JUC
類庫是親和的,可以把虛擬線程替換原來JUC
類庫中的Thread
實例來嘗試使用(在生產應用建議等該功能正式發佈)
向量API
向量API
目前是第四次孵化,功能是表達向量計算,在運行時編譯為CPU
架構上的最佳向量指令,從而實現優於等效標量計算的性能。目前相關API
都在jdk.incubator.vector
包下,使用的例子如下:
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256; private static void vectorComputation(float[] a, float[] b, float[] c) { for (int i = 0; i < a.length; i += SPECIES.length()) { var m = SPECIES.indexInRange(i, a.length); var va = FloatVector.fromArray(SPECIES, a, i, m); var vb = FloatVector.fromArray(SPECIES, b, i, m); var vc = va.mul(va).add(vb.mul(vb)).neg(); vc.intoArray(c, i, m); } } public static void main(String[] args) { float[] a = new float[]{1.0f, 3.0f, 2.0f}; float[] b = {1.0f, -1.0f, 5.0f}; float[] c = {1.0f, 6.0f, 1.0f}; vectorComputation(a, b, c); System.out.println(Arrays.toString(c)); }
Vector
有很多特化子類,可以通過不同的VectorSpecies
進行定義。
switch匹配模式
switch
匹配模式第三次預覽,主要是對匹配模式進行瞭擴展。主要有幾點改進:
- 增強類型校驗,
case
子句支持多種類型
record Point(int i, int j) {} enum Color { RED, GREEN, BLUE; } private void multiTypeCase(Object o) { switch (o) { case null -> System.out.println("null"); case String s -> System.out.println("String"); case Color c -> System.out.println("Color: " + c.toString()); case Point p -> System.out.println("Record class: " + p.toString()); case int[] ia -> System.out.println("Array of ints of length" + ia.length); default -> System.out.println("Something else"); } }
- 增強表達式和語句的表現力和適用性,可以實現
selector
模式
private int selector(Object o) { return switch (o) { case String s -> s.length(); case Integer i -> i; default -> 0; }; }
- 擴展模式變量聲明范圍
private void switchScope(Object o) { switch (o) { case Character c when c.charValue() == 7: System.out.println("Seven!"); break; default: break; } }
- 優化
null
處理
private void switchNull(Object o) { switch (o) { case null -> System.out.println("null!"); case String s -> System.out.println("String"); default -> System.out.println("Something else"); } }
結構化並發
結構化並發功能在孵化階段,該功能旨在通過結構化並發庫來簡化多線程編程。結構化並發提供的特性將在不同線程中運行的多個任務視為一個工作單元,以簡化錯誤處理和取消,提高瞭可靠性和可觀測性。
record User(String name, Long id){} record Order(String orderNo, Long id){} record Response(User user, Order order){} private User findUser(){ throw new UnsupportedOperationException("findUser"); } private Order fetchOrder(){ throw new UnsupportedOperationException("fetchOrder"); } private Response handle() throws ExecutionException, InterruptedException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Future<User> user = scope.fork(() -> findUser()); Future<Order> order = scope.fork(() -> fetchOrder()); scope.join(); // Join both forks scope.throwIfFailed(); // ... and propagate errors // Here, both forks have succeeded, so compose their results return new Response(user.resultNow(), order.resultNow()); } }
參考資料
JDK 19
:https://openjdk.org/projects/jdk/19
,文中直接應用部分文檔描述的翻譯
以上就是JDK19新特性使用實例詳解的詳細內容,更多關於JDK19新特性的資料請關註WalkonNet其它相關文章!