App下載

從 Java 12 到 17 的新特性

愿你無(wú)恙 2021-08-27 10:11:38 瀏覽數(shù) (2355)
反饋

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á)式

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.jarlib目錄中有一個(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ò)展)


0 人點(diǎn)贊