JDK19新特性使用詳解
前提
JDK19於2022-09-20發布GA版本,本文將會詳細介紹JDK19新特性的使用。
新特性列表
新特性列表如下:
JPE-405:Record模式(預覽功能)JPE-422:JDK移植到Linux/RISC-VJPE-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://openjdk.org/projects/jdk/19,文中直接應用部分文檔描述的翻譯
(本文完,c-2-d e-a-20220923)
