App下載

從 Java 12 到 17 的新特性

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

Java 11(迄今為止的最后一個長期支持版本)三年后,Java 17 LTS 將于 2021 年 9 月發(fā)布。是時候快速瀏覽一下開發(fā)人員從 11 升級到 17 后可以享受的新功能了。請注意,在幕后進行了更多改進。本文重點介紹大多數(shù)開發(fā)人員可以直接使用的功能:

開關(guān)表達式

switch 現(xiàn)在可以返回一個值,就像一個表達式:

// 將給定 planet 的 group 分配給一個變量
String group = switch(planet){
    case MERCURY, VENUS, EARTH, MARS -> "內(nèi)行星";
    case JUPITER, SATURN, URANUS, NEPTUNE -> "外行星";
}

如果單個 case 的右側(cè)需要更多代碼,則可以將其寫入塊中,并使用yield以下方法返回值:

// 打印給定 planet 的 group,以及更多信息
// 并將給定 planet 的 group 分配給一個變量
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)簽進行切換不需要返回值,就像 void 表達式一樣:

// 打印給定 planet 的 group
// 不返回任何東西
switch(planet){
    case EARTH, MARS -> System.out.println("內(nèi)行星");
    case JUPITER, SATURN -> System.out.println("外行星");
}

與傳統(tǒng)的 switch 相比,新的 switch 表達式

  • 使用“->”代替“:”
  • 每個案例允許多個常量
  • 沒有貫穿語義(即,不需要中斷)
  • 使在 case 分支中定義的變量本地化到這個分支

此外,編譯器保證了 switch 的詳盡性,因為只有一種情況被執(zhí)行,這意味著要么

  • 所有可能的值都被列為案例(如上面的枚舉由八個行星組成)
  • 必須提供“默認(rèn)”分支

文本塊

文本塊允許編寫包含雙引號的多行字符串,而無需使用\n\"轉(zhuǎn)義序列:

String block = """
    可以輸入多行文本內(nèi)容
        可以縮進
            可以帶有“雙引號”!
"""

文本塊由三個雙引號"""和一個換行符打開,并由三個雙引號關(guān)閉。

Java 編譯器應(yīng)用智能算法從結(jié)果字符串中去除前導(dǎo)空格,使得

  • 刪除了僅與更好的 Java 源代碼可讀性相關(guān)的縮進
  • 與字符串本身相關(guān)的縮進保持不變

在上面的示例中,結(jié)果字符串如下所示,其中每個都.標(biāo)記一個空格:

..可以輸入多行文本內(nèi)容
....可以縮進
......可以帶有“雙引號”!

想象一個跨越文本塊高度的垂直條,從左到右移動并刪除空格,直到它接觸到第一個非空格字符。結(jié)束文本塊分隔符也很重要,因此將其向左移動兩個位置。

String block = """
    可以輸入多行文本內(nèi)容
        可以縮進
            可以帶有“雙引號”!
"""

結(jié)果在以下字符串中:

可以輸入多行文本內(nèi)容
..可以縮進
....可以帶有“雙引號”!

此外,每一行的尾隨空格都會被刪除,這可以通過使用新的轉(zhuǎn)義序列來防止\s。

文本塊內(nèi)的換行符可以轉(zhuǎn)義:

String block = """
    請 \
    不要 \
    插隊 \
    , \
    謝謝 \
"""

結(jié)果在以下字符串中,沒有任何換行符:

請.不要.插隊.,.謝謝

或者,也可以通過將結(jié)束定界符直接附加到字符串的末尾來刪除最后的換行符

String block = """
    沒有最終的斷線
        在這個字符串的末端"""

將變量插入文本塊可以像往常一樣使用靜態(tài)方法String::format或新的實例方法String::formatted來完成,寫起來要短一些:

String block ="""
    %s 標(biāo)記位置.
    """.formatted("x");

打包工具

假設(shè)您demo.jarlib目錄中有一個 JAR 文件,以及其他依賴項 JAR。以下命令

jpackage --name demo --input lib --main-jar demo.jar --main-class demo.Main

將此演示應(yīng)用程序打包為與您當(dāng)前平臺相對應(yīng)的本機格式:

  • Linux:deb 或 rpm
  • Windows:msi 或 exe
  • macOS:pkg 或 dmg

生成的包還包含運行應(yīng)用程序所需的 JDK 部分以及本機啟動器。這意味著用戶可以以特定于平臺的標(biāo)準(zhǔn)方式安裝、運行和卸載應(yīng)用程序,而無需事先明確安裝 Java。

不支持交叉編譯:如果需要 Windows 用戶的包,必須在 Windows 機器上用 jpackage 創(chuàng)建。

可以使用更多選項自定義包創(chuàng)建,這些選項記錄在jpackage 手冊頁上

Instanceof 的模式匹配

模式匹配instanceof消除了在類型比較后執(zhí)行強制轉(zhuǎn)換的樣板代碼:

Object o = "字符串偽裝成對象";
if (o instanceof String s){
    System.out.println(s.toUppperCase());
}

在上面的示例中,新變量的范圍s直觀地限于if分支。準(zhǔn)確地說,變量在保證模式匹配的范圍內(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());

記錄

記錄減少了作為簡單數(shù)據(jù)載體的類的樣板代碼:

record Point(int x, int y) { }

這個單行產(chǎn)生一個自動定義的記錄類

  • x 和 y 的字段(私有和最終)
  • 所有字段的規(guī)范構(gòu)造函數(shù)
  • 所有領(lǐng)域的?Getters?
  • ?equals?, ?hashCode?, 和?toString?(考慮所有字段)
// 規(guī)范構(gòu)造函數(shù)
Point p = new Point(1, 2);
// getters - 沒有'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] 

記錄類的一些最重要的限制是它們

  • 是不可變的(因為它們的字段是私有的和最終的)
  • 是隱式最終的
  • 無法定義其他實例字段
  • 總是擴展?Record?類

然而,也可以:

  • 定義其他方法
  • 實現(xiàn)接口
  • 自定義規(guī)范構(gòu)造函數(shù)和訪問器
record Point(int x, int y) {
  // 顯示規(guī)范構(gòu)造函數(shù)
  Point {
    // 自定義驗證
    if (x < 0 || y < 0) 
      throw new IllegalArgumentException("no negative points allowed");
    // 自定義調(diào)整(通常違背直覺)
    x += 1000;
    y += 1000;
    // 對字段的賦值在最后自動法僧
  }
  // 顯示訪問器
  public int x() {
    // 自定義的代碼
    return this.x;
  }
}

此外,可以在方法中定義本地記錄:

public void withLocalRecord() {
  record Point(int x, int y) { };
  Point p = new Point(1, 2);
}

密封類

密封類明確列出允許的直接子類。其他類不得從此類擴展:

public sealed class Parent
  permits ChildA, ChildB, ChildC { ... }

同樣,密封接口明確列出允許的直接子接口和實現(xiàn)類:

sealed interface Parent
  permits ChildA, ChildB, ChildC { ... }

列表中的類或接口permits必須位于同一個包中(如果父模塊位于命名模塊中,則位于同一個模塊中)。

所述permits,如果亞類(或接口)位于同一文件內(nèi),可以省略列表:

public sealed class Parent {
  final class Child1 extends Parent {}
  final class Child2 extends Parent {}
  final class Child3 extends Parent {}
}

permits列表中的每個子類或接口都必須使用以下修飾符之一:

  • final(不允許進一步擴展;僅用于子類,因為接口不能是最終的)
  • 密封(允許進一步、有限的擴展)
  • 非密封(允許再次無限擴展)


0 人點贊