Java 11(迄今為止的最后一個(gè)長(zhǎng)期支持版本)三年后,Java 17 LTS 將于 2021 年 9 月發(fā)布。是時(shí)候快速瀏覽一下開(kāi)發(fā)人員從 11 升級(jí)到 17 后可以享受的新功能了。請(qǐng)注意,在幕后進(jìn)行了更多改進(jìn)。本文重點(diǎn)介紹大多數(shù)開(kāi)發(fā)人員可以直接使用的功能:
- 開(kāi)關(guān)表達(dá)式 ( JEP 361 )
- 文本塊 ( JEP 378 )
- 封裝工具 ( JEP 392 )
- instanceof 的模式匹配(JEP 394)
- 記錄 ( JEP 395 )
- 密封類 ( JEP 409 )
開(kāi)關(guān)表達(dá)式
switch 現(xiàn)在可以返回一個(gè)值,就像一個(gè)表達(dá)式:
// 將給定 planet 的 group 分配給一個(gè)變量
String group = switch(planet){
case MERCURY, VENUS, EARTH, MARS -> "內(nèi)行星";
case JUPITER, SATURN, URANUS, NEPTUNE -> "外行星";
}
如果單個(gè) case 的右側(cè)需要更多代碼,則可以將其寫入塊中,并使用yield
以下方法返回值:
// 打印給定 planet 的 group,以及更多信息
// 并將給定 planet 的 group 分配給一個(gè)變量
String group = switch(planet){
case EARTH, MARS -> {
System.out.println("內(nèi)行星");
System.out.println("主要由巖石組成");
yield "內(nèi)部的";
}
case JUPITER, SATURN -> {
System.out.println("外行星");
System.out.println("主要由氣體組成");
yield "外部的";
}
}
但是,使用新的箭頭標(biāo)簽進(jìn)行切換不需要返回值,就像 void 表達(dá)式一樣:
// 打印給定 planet 的 group
// 不返回任何東西
switch(planet){
case EARTH, MARS -> System.out.println("內(nèi)行星");
case JUPITER, SATURN -> System.out.println("外行星");
}
與傳統(tǒng)的 switch 相比,新的 switch 表達(dá)式
- 使用“->”代替“:”
- 每個(gè)案例允許多個(gè)常量
- 沒(méi)有貫穿語(yǔ)義(即,不需要中斷)
- 使在 case 分支中定義的變量本地化到這個(gè)分支
此外,編譯器保證了 switch 的詳盡性,因?yàn)橹挥幸环N情況被執(zhí)行,這意味著要么
- 所有可能的值都被列為案例(如上面的枚舉由八個(gè)行星組成)
- 必須提供“默認(rèn)”分支
文本塊
文本塊允許編寫包含雙引號(hào)的多行字符串,而無(wú)需使用\n
或\"
轉(zhuǎn)義序列:
String block = """
可以輸入多行文本內(nèi)容
可以縮進(jìn)
可以帶有“雙引號(hào)”!
"""
文本塊由三個(gè)雙引號(hào)"""
和一個(gè)換行符打開(kāi),并由三個(gè)雙引號(hào)關(guān)閉。
Java 編譯器應(yīng)用智能算法從結(jié)果字符串中去除前導(dǎo)空格,使得
- 刪除了僅與更好的 Java 源代碼可讀性相關(guān)的縮進(jìn)
- 與字符串本身相關(guān)的縮進(jìn)保持不變
在上面的示例中,結(jié)果字符串如下所示,其中每個(gè)都.
標(biāo)記一個(gè)空格:
..可以輸入多行文本內(nèi)容
....可以縮進(jìn)
......可以帶有“雙引號(hào)”!
想象一個(gè)跨越文本塊高度的垂直條,從左到右移動(dòng)并刪除空格,直到它接觸到第一個(gè)非空格字符。結(jié)束文本塊分隔符也很重要,因此將其向左移動(dòng)兩個(gè)位置。
String block = """
可以輸入多行文本內(nèi)容
可以縮進(jìn)
可以帶有“雙引號(hào)”!
"""
結(jié)果在以下字符串中:
可以輸入多行文本內(nèi)容
..可以縮進(jìn)
....可以帶有“雙引號(hào)”!
此外,每一行的尾隨空格都會(huì)被刪除,這可以通過(guò)使用新的轉(zhuǎn)義序列來(lái)防止\s
。
文本塊內(nèi)的換行符可以轉(zhuǎn)義:
String block = """
請(qǐng) \
不要 \
插隊(duì) \
, \
謝謝 \
"""
結(jié)果在以下字符串中,沒(méi)有任何換行符:
請(qǐng).不要.插隊(duì).,.謝謝
或者,也可以通過(guò)將結(jié)束定界符直接附加到字符串的末尾來(lái)刪除最后的換行符
String block = """
沒(méi)有最終的斷線
在這個(gè)字符串的末端"""
將變量插入文本塊可以像往常一樣使用靜態(tài)方法String::format
或新的實(shí)例方法String::formatted
來(lái)完成,寫起來(lái)要短一些:
String block ="""
%s 標(biāo)記位置.
""".formatted("x");
打包工具
假設(shè)您demo.jar
在lib
目錄中有一個(gè) JAR 文件,以及其他依賴項(xiàng) JAR。以下命令
jpackage --name demo --input lib --main-jar demo.jar --main-class demo.Main
將此演示應(yīng)用程序打包為與您當(dāng)前平臺(tái)相對(duì)應(yīng)的本機(jī)格式:
- Linux:deb 或 rpm
- Windows:msi 或 exe
- macOS:pkg 或 dmg
生成的包還包含運(yùn)行應(yīng)用程序所需的 JDK 部分以及本機(jī)啟動(dòng)器。這意味著用戶可以以特定于平臺(tái)的標(biāo)準(zhǔn)方式安裝、運(yùn)行和卸載應(yīng)用程序,而無(wú)需事先明確安裝 Java。
不支持交叉編譯:如果需要 Windows 用戶的包,必須在 Windows 機(jī)器上用 jpackage 創(chuàng)建。
可以使用更多選項(xiàng)自定義包創(chuàng)建,這些選項(xiàng)記錄在jpackage 手冊(cè)頁(yè)上。
Instanceof 的模式匹配
模式匹配instanceof
消除了在類型比較后執(zhí)行強(qiáng)制轉(zhuǎn)換的樣板代碼:
Object o = "字符串偽裝成對(duì)象";
if (o instanceof String s){
System.out.println(s.toUppperCase());
}
在上面的示例中,新變量的范圍s
直觀地限于if
分支。準(zhǔn)確地說(shuō),變量在保證模式匹配的范圍內(nèi),這也使以下代碼有效:
if (o instanceof String s && !s.isEmpty()){
System.out.println(s.toUpperCase());
}
反之亦然
if (!(o instanceof String s)){
throw new RuntimeException("excepting string");
}
// s 在此范圍內(nèi)!
System.out.println(s.toUpperCase());
記錄
記錄減少了作為簡(jiǎn)單數(shù)據(jù)載體的類的樣板代碼:
record Point(int x, int y) { }
這個(gè)單行產(chǎn)生一個(gè)自動(dòng)定義的記錄類
- x 和 y 的字段(私有和最終)
- 所有字段的規(guī)范構(gòu)造函數(shù)
- 所有領(lǐng)域的?
Getters
? - ?
equals
?, ?hashCode
?, 和?toString
?(考慮所有字段)
// 規(guī)范構(gòu)造函數(shù)
Point p = new Point(1, 2);
// getters - 沒(méi)有'get'前綴
p.x();
p.y();
// equals, hashCode, toString
p.equals(new Point(1, 2)); // true
p.hashCode(); // 依賴于x和y的值
p.toString(); // Point[x=1,y=2]
記錄類的一些最重要的限制是它們
- 是不可變的(因?yàn)樗鼈兊淖侄问撬接械暮妥罱K的)
- 是隱式最終的
- 無(wú)法定義其他實(shí)例字段
- 總是擴(kuò)展?
Record
?類
然而,也可以:
- 定義其他方法
- 實(shí)現(xiàn)接口
- 自定義規(guī)范構(gòu)造函數(shù)和訪問(wèn)器
record Point(int x, int y) {
// 顯示規(guī)范構(gòu)造函數(shù)
Point {
// 自定義驗(yàn)證
if (x < 0 || y < 0)
throw new IllegalArgumentException("no negative points allowed");
// 自定義調(diào)整(通常違背直覺(jué))
x += 1000;
y += 1000;
// 對(duì)字段的賦值在最后自動(dòng)法僧
}
// 顯示訪問(wèn)器
public int x() {
// 自定義的代碼
return this.x;
}
}
此外,可以在方法中定義本地記錄:
public void withLocalRecord() {
record Point(int x, int y) { };
Point p = new Point(1, 2);
}
密封類
密封類明確列出允許的直接子類。其他類不得從此類擴(kuò)展:
public sealed class Parent
permits ChildA, ChildB, ChildC { ... }
同樣,密封接口明確列出允許的直接子接口和實(shí)現(xiàn)類:
sealed interface Parent
permits ChildA, ChildB, ChildC { ... }
列表中的類或接口permits
必須位于同一個(gè)包中(如果父模塊位于命名模塊中,則位于同一個(gè)模塊中)。
所述permits
,如果亞類(或接口)位于同一文件內(nèi),可以省略列表:
public sealed class Parent {
final class Child1 extends Parent {}
final class Child2 extends Parent {}
final class Child3 extends Parent {}
}
permits
列表中的每個(gè)子類或接口都必須使用以下修飾符之一:
- final(不允許進(jìn)一步擴(kuò)展;僅用于子類,因?yàn)榻涌诓荒苁亲罱K的)
- 密封(允許進(jìn)一步、有限的擴(kuò)展)
- 非密封(允許再次無(wú)限擴(kuò)展)