Java 13 新功能介紹

自從 Oracle 調(diào)整了 Java 的版本發(fā)布節(jié)奏之后,Java 版本發(fā)布越來越快,雖然都說 Java 版本任他發(fā),我用 Java 8,不過新版本的 Java 功能還是要學(xué)習(xí)一下的。
Java 13 早在 2019 年 9 月就已經(jīng)發(fā)布,雖然不是長久支持版本,但是也帶來了不少新功能。
Java 13 官方下載:https://jdk.java.net/archive/
Java 13 官方文檔:http://openjdk.java.net/projects/jdk/13/
Java 13 新功能:
JEP 350: 動態(tài) CDS 存檔 JEP 351: ZGC,歸還未使用的內(nèi)存 (實驗性) JEP 353: 重新實現(xiàn) Socket API JEP 354: Switch 表達式 (二次預(yù)覽) JEP 355: 文本塊 (預(yù)覽)
擴展:此文章屬于 Java 新特性教程 系列,會介紹 Java 每個版本的新功能,可以點擊瀏覽。
1. JEP 350 動態(tài) CDS 存檔
JVM 啟動時有一步是需要在內(nèi)存中加載類,而如果有多個 jar,加載第一個 jar 的速度是最慢的。這就延長了程序的啟動時間,為了減少這個時間,Java 10 引入了應(yīng)用程序類數(shù)據(jù)共享(CDS)機制,它可以把你想共享的類共享在程序之間,使不同的 Java 進程之間共享這個類來減少這個類占用的空間以及加載速度。不過 Java 10 中使用這個功能的步驟比較繁瑣。
擴展閱讀:Java 10 新功能介紹
而 Java 13 中的 AppCDS,允許 Java 應(yīng)用在程序執(zhí)行結(jié)束時(如果 JVM 沒有崩潰)進行動態(tài)存檔;存儲的內(nèi)容包括所有加載的應(yīng)用類型類和使用的類庫,這些存儲的類庫本來并不存在于默認的 CDS 存檔中。
使用這個功能非常簡單,只需要在程序啟動時增加啟動參數(shù) 。
# ArchiveClassesAtExit,程序結(jié)束時動態(tài)存檔
bin/java -XX:ArchiveClassesAtExit=hello.jsa -cp hello.jar Hello
# SharedArchiveFile,使用指定存檔啟動
bin/java -XX:SharedArchiveFile=hello.jsa -cp hello.jar Hello
2. JEP 351: ZGC,歸還未使用的內(nèi)存 (實驗性)
在 Java 13 之前,ZGC 雖然在清理內(nèi)存時導(dǎo)致的停頓時間非常少,但是即使內(nèi)存已經(jīng)長時間沒有使用,ZGC 也不會將內(nèi)存返還給操作系統(tǒng),這對那些十分關(guān)注內(nèi)存占用的應(yīng)用程序非常不友好。
比如:
資源按使用量付費的云上容器環(huán)境。
應(yīng)用雖然長時間閑置,但是占用了內(nèi)存,導(dǎo)致運行的其他程序內(nèi)存緊張。
而新增的這個功能,可以讓 ZGC 歸還長時間沒有使用的內(nèi)存給操作系統(tǒng),這對某些用戶來說十分友好。
3. JEP 353: 重新實現(xiàn) Socket API
java.net.Socket 和 java.net.ServerSocket 類早在 Java 1.0 時就已經(jīng)引入了,它們的實現(xiàn)的 Java 代碼和 C 語言代碼的混合,維護和調(diào)試都十分不易;而且這個實現(xiàn)還存在并發(fā)問題,有時候排查起來也很困難。
因此,在 Java 13 中引入了新的實現(xiàn)方式,使用了新的實現(xiàn) NioSocketImpl 來代替老舊的 PlainSocketImpl 實現(xiàn)。雖然功能相同,但是老的方式在當(dāng)前以及未來幾個版本內(nèi)不會刪除,用戶隨時可以通過 -Djdk.net.usePlainSocketImpl 參數(shù)切換回老的實現(xiàn)方式,以兼容意外情況。
編寫一個測試類可以發(fā)現(xiàn)這個變化。
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Test {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8000)){
boolean running = true;
while(running){
Socket clientSocket = serverSocket.accept();
//do something with clientSocket
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用 Java 13 運行,通過參數(shù) -XX:+TraceClassLoading 追蹤加載的類,日志中可以看到 NioSocketImpl。
? develop ./jdk-13.0.2.jdk/Contents/Home/bin/java -XX:+TraceClassLoading Test.java | grep SocketImpl
[0.699s][info ][class,load] java.net.SocketImpl source: jrt:/java.base
[0.699s][info ][class,load] java.net.SocketImpl$$Lambda$173/0x0000000800c37440 source: java.net.SocketImpl
[0.702s][info ][class,load] sun.net.PlatformSocketImpl source: jrt:/java.base
[0.702s][info ][class,load] sun.nio.ch.NioSocketImpl source: jrt:/java.base
[0.713s][info ][class,load] sun.nio.ch.NioSocketImpl$FileDescriptorCloser source: jrt:/java.base
但在 Java 12 并不是 NioSocketImpl。
? develop ./jdk-12.0.2.jdk/Contents/Home/bin/java -XX:+TraceClassLoading Test.java | grep SocketImpl
[0.665s][info ][class,load] java.net.SocketImpl source: jrt:/java.base
[0.665s][info ][class,load] java.net.AbstractPlainSocketImpl source: jrt:/java.base
[0.665s][info ][class,load] java.net.PlainSocketImpl source: jrt:/java.base
[0.665s][info ][class,load] java.net.SocksSocketImpl source: jrt:/java.base
[0.666s][info ][class,load] java.net.AbstractPlainSocketImpl$1 source: jrt:/java.base
4. JEP 354: Switch 表達式 (二次預(yù)覽)
你為什么不愿意使用使用 switch 表達式?我想其中一個原因應(yīng)該是, switch 表達式的代碼不夠美觀優(yōu)雅,甚至有些啰嗦。比如像下面的例子。
package com.wdbyte;
public class Java13Switch {
public static void main(String[] args) {
//通過傳入月份,輸出月份所屬的季節(jié)
System.out.println(switchJava12Before("march"));
}
public static String switchJava12Before(String month) {
String reuslt = null;
switch (month) {
case "march":
case "april":
case "may":
reuslt = "春天";
break;
case "june":
case "july":
case "august":
reuslt = "夏天";
break;
case "september":
case "october":
case "november":
reuslt = "秋天";
break;
case "december":
case "january":
case "february":
reuslt = "冬天";
break;
}
return reuslt;
}
}
而在 Java 12 中,已經(jīng)對 switch 進行了改進,使之可以使用 cast L -> 表達式進行操作,且可以具有返回值,這樣代碼就更加美觀使用了,不過這在 Java 12 中是一個預(yù)覽功能。
// 通過傳入月份,輸出月份所屬的季節(jié)
public static String switchJava12(String month) {
return switch (month) {
case "march", "april", "may" -> "春天";
case "june", "july", "august" -> "夏天";
case "september", "october", "november" -> "秋天";
case "december", "january", "february" -> "冬天";
default -> "month erro";
};
}
擴展閱讀:Java 12 新特性介紹
而現(xiàn)在,在 Java 13 中,又對 switch 表達式進行了增強,增加了yield 關(guān)鍵詞用于返回值,相比 break ,語義更加明確了。
public static String switchJava13(String month) {
return switch (month) {
case "march", "april", "may":
yield "春天";
case "june", "july", "august":
yield "夏天";
case "september", "october", "november":
yield "秋天";
case "december", "january", "february":
yield "冬天";
default:
yield "month error";
};
}
5. JEP 355: 文本塊 (預(yù)覽)
在這之前,如果我們把一個 JSON 賦值給字符串:
String content = "{\n"
+ " \"upperSummary\": null,\n"
+ " \"sensitiveTypeList\": null,\n"
+ " \"gmtModified\": \"2011-08-05 10:50:09\",\n"
+ " \"lowerGraph\": null,\n"
+ " \"signature\": \"\",\n"
+ " \"appName\": \"xxx\",\n"
+ " \"lowerSummary\": null,\n"
+ " \"gmtCreate\": \"2011-08-05 10:50:09\",\n"
+ " \"type\": \"CALL\",\n"
+ " \"name\": \"xxxx\",\n"
+ " \"subType\": \"yyy\",\n"
+ " \"id\": 1,\n"
+ " \"projectId\": 1,\n"
+ " \"status\": 1\n"
+ "}";
終于不用寫丑陋的長字符串了,從 Java 13 開始你可以使用文本塊的方式定義字符串了。
String content2 = """
{
"upperSummary": null,
"sensitiveTypeList": null,
"gmtModified": "2011-08-05 10:50:09",
"lowerGraph": null,
"signature": "",
"appName": "xxx",
"lowerSummary": null,
"gmtCreate": "2011-08-05 10:50:09",
"type": "CALL",
"name": "xxxx",
"subType": "yyy",
"id": 1,
"projectId": 1,
"status": 1
}
""";
不過這是一個預(yù)覽功能,如果你要是在 Java 13 中使用需要手動開啟預(yù)覽功能,這個功能在 Java 15 中正式發(fā)布。
如果你想嘗試,可以去下載最新版了,玩的開心。
參考
http://openjdk.java.net/projects/jdk/13/ https://nipafx.dev/java-application-class-data-sharing/ https://www.wdbyte.com/2020/02/jdk/jdk12-feature/ https://mkyong.com/java/what-is-new-in-java-13/
