JDK14新特徵最全詳解

公众号-JavaEdge發表於2024-07-14

JDK 14一共發行了16個JEP(JDK Enhancement Proposals,JDK 增強提案),篩選出JDK 14新特性。

- 343: 打包工具 (Incubator)

- 345: G1的NUMA記憶體分配最佳化

- 349: JFR事件流

- 352: 非原子性的位元組緩衝區對映

- 358: 友好的空指標異常

- 359: Records (預覽)

- 361: Switch表示式 (標準)

- 362: 棄用Solaris和SPARC埠

- 363: 移除CMS(Concurrent Mark Sweep)垃圾收集器

- 364: macOS系統上的ZGC

- 365: Windows系統上的ZGC

- 366: 棄用ParallelScavenge + SerialOld GC組合

- 367: 移除Pack200 Tools 和 API

- 368: 文字塊 (第二個預覽版)

- 370: 外部儲存器API (Incubator)

1 JEP 305的instanceof運算子

JEP 305新增使instanceof運算子具有模式匹配的能力。模式匹配使程式通用邏輯更簡潔,程式碼更簡單,同時在做型別判斷和型別轉換時也更安全。

設計初衷

包含判斷表示式是否具有某種型別的邏輯時,程式會對不同型別進行不同的處理。

instanceof-and-cast

// 在方法的入口接收一個物件
public void beforeWay(Object obj) {
    // 透過instanceof判斷obj物件的真實資料型別是否是String型別
    if (obj instanceof String) {
        // 如果進來了,說明obj的型別是String型別,直接進行強制型別轉換。
        String str = (String) obj;
        // 輸出字串的長度
        System.out.println(str.length());
    }
}
  1. 先判斷obj的真實資料型別
  2. 判斷成立後進行了強制型別轉換(將物件obj強制型別轉換為String)
  3. 宣告一個新的本地變數str,指向上面的obj

但上述做法不是最理想:

  • 語法臃腫乏味
  • 同時執行型別檢測校驗和型別轉換
  • String型別出現3次,但最終要的可能只是一個String型別的物件變數
  • 重複程式碼過多,冗餘度高

JDK14提供新解決方案:新的instanceof模式匹配 ,用法如下,在instanceof的型別之後新增變數str

instanceofobj的型別檢查透過,obj會被轉換成str表示的String型別。在新用法中,String型別僅出現一次:

public void patternMatching(Object obj) {
    if (obj instanceof String str) {
          // can use str here
        System.out.println(str.length());
    } else {
        // can't use str here
    }
}

注意:若obj是String型別,則將obj型別轉換為String,並將其賦值給變數str。繫結的變數作用域為if語句內部,並不在false語句塊內。

簡化equals()

equals()一般先檢查目標物件的型別。instanceof的模式匹配可簡化equals()實現。

public class Student {
  private  String name ;

  public Student(String name) {
    this.name = name;
  }

//    @Override
//    public boolean equals(Object o) {
//        if (this == o) return true;
//        if (o == null || getClass() != o.getClass()) return false;
//        Student student = (Student) o;
//        return Objects.equals(name, student.name);
//    }
  // 簡化後做法!  
  @Override
  public boolean equals(Object obj) {
    return (obj instanceof Student s) && Objects.equals(this.name, s.name);
  }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

JDK 14後的instanceof的模式匹配極大的簡化了型別檢查和轉型的問題。

2 JEP 361:Switch Expressions (Standard)

擴充套件switch分支選擇語句的寫法。Switch表示式在經過JDK 12(JEP 325)和JDK(JEP 354)的預覽之後,在JDK 14中已穩定可用。

2.1 設計初衷

switch語句是個變化較大的語法。可是因為Java switch一直不夠強大、熟悉swift或js語言的同學可對比發現,因為Java的很多版本都在不斷改進switch語句:JDK 12擴充套件了switch語句,使其可用作語句或表示式,並且傳統的和擴充套件的簡化版switch都可以使用。

JDK 12對於switch的增強主要在於簡化書寫形式,提升功能點。

2.2 switch的進化

  • Java 5+開始,可用列舉
  • Java 7+開始,支援使用String型別的變數和表示式
  • Java 11+開始,自動對省略break導致的貫穿提示警告(以前需用-X:fallthrough選項才能顯示)

從JDK12開始有大程度增強,JDK 14的該JEP是從JEP 325JEP 354演變而來。但是,此JEP 361 Switch表示式 (標準)是獨立的,並不依賴於這倆JEP。

2.3 以前的switch程式

public class Demo01 {
    public static void main(String[] args){
        // 宣告變數score,併為其賦值為'C'
        var score = 'C';
        // 執行switch分支語句
        switch (score) {
            case 'A':
                System.out.println("優秀");
                break;
            case 'B':
                System.out.println("良好");
                break;
            case 'C':
                System.out.println("中");
                break;
            case 'D':
                System.out.println("及格");
                break;
            case 'E':
                System.out.println("不及格");
                break;
            default:
                System.out.println("資料非法!");
        }
    }
}

經典的Java 11前的寫法 ,不能忘寫break,否則switch就會貫穿、導致程式出現錯誤(JDK 11會提示警告)。

2.4 無需break

在JDK 12前switch忘寫break將導致貫穿,在JDK 12對switch的這一貫穿性做了改進。只要將case後面的":"改成箭頭,即使不寫break也不會貫穿,因此上面程式可改寫為:

public class Demo02{
    public static void main(String[] args){
        // 宣告變數score,併為其賦值為'C'
        var score = 'C';
        // 執行switch分支語句
        switch (score){
            case 'A' -> System.out.println("優秀");
            case 'B' -> System.out.println("良好");
            case 'C' -> System.out.println("中");
            case 'D' -> System.out.println("及格");
            case 'E' -> System.out.println("不及格");
            default -> System.out.println("成績資料非法!");
        }
    }
}

簡潔很多了。注意,新老語法不能混用了,否則編譯報錯:

java: 在 switch 中使用了不同 case 型別:

2.5 JDK 14的switch表示式

JDK 12後的switch甚至可作為表示式,不再是單獨的語句,如:

public class Demo03 {
    public static void main(String[] args) {
        // 宣告變數score,併為其賦值為'C'
        var score = 'C';
        // 執行switch分支語句
        String s = switch (score)
                {
                    case 'A' -> "優秀";
                    case 'B' -> "良好";
                    case 'C' -> "中";
                    case 'D' -> "及格";
                    case 'F' -> "不及格";
                    default -> "成績輸入錯誤";
                };
        System.out.println(s);
    }
}

上面程式直接將switch表示式的值賦值給s變數,這樣switch不再是一個語句,而是一個表示式。

2.6 多值匹配

當你把switch中的case後的冒號改為箭頭之後,此時switch就不會貫穿,但某些情況,程式本就希望貫穿。比如希望兩個case共用一個執行體!

JDK 12之後的switch中的case也支援多值匹配,這樣程式就變得更加簡潔了,如:

public class Demo04 {
    public static void main(String[] args) {
				 // 宣告變數score,併為其賦值為'C'
        var score = 'B';
				 
        // 執行switch分支語句
        String s = switch (score)
                {
                    case 'A', 'B' -> "上等";
                    case 'C' -> "中等";
                    case 'D', 'E' -> "下等";
                    default -> "成績資料輸入非法!";
                };
        System.out.println(s);
    }
}

2.7 Yielding a value

用箭頭標籤時,箭頭標籤右邊可為表示式、throw語句或是程式碼塊。

如是程式碼塊,需用yield語句來返回值。下面程式碼中的print方法中的default語句的右邊是一個程式碼塊。在程式碼塊中使用yield來返回值。

JDK 14引入yield語句產生一個值,該值成為封閉的switch表示式的值。

public void print(int days) {
  // 宣告變數score,併為其賦值為'C'
  var score = 'B';
  String result = switch (score) {
      case 'A', 'B' -> "上等";
      case 'C' -> "中等";
      case 'D', 'E' -> "下等";
      default -> {
          if (score > 100) {
            yield "資料不能超過100";
          } else {
            yield score + "此分數低於0分";
          }
      }
  };
  System.out.println(result);
}

在switch表示式中不能使用break。switch表示式的每個標籤都必須產生一個值或拋異常。

switch表示式須窮盡所有可能值。這意味著通常需要一個default語句。一個例外是列舉型別,如窮盡了列舉型別的所有可能值,則不需要使用default。在這種情況下,編譯器會自動生成一個default語句。這是因為列舉型別中的列舉值可能發生變化。

如列舉型別Color 中原來只有3個值:RED、GREEN和BLUE。使用該列舉型別的switch表示式窮盡了3種情況並完成編譯。之後Color中增加了一個新的值YELLOW,當用這個新的值呼叫之前的程式碼時,由於不能匹配已有的值,編譯器產生的default會被呼叫,告知列舉型別發生改變

3 JEP 368:文字塊 (JDK 13後的第二個預覽版)

3.1 引入

Java開發通常需進行大量字串文字拼接等相關組織操作,從JDK 13到JDK 14開始文字塊新特性,提高Java程式書寫大段字串文字的可讀性和方便性。

3.2 設計初衷

文字塊功能在JDK 13中作為預覽功能(JEP 355)被引入。這個功能在JDK 14中得到了更新。文字塊是使用3個引號分隔的多行字串。

3.3 描述

文字塊可表示任何字串,具有更高表達能力和更少複雜度。

文字塊開頭定界符是("""),後面跟0或多個空格,最後跟一個行終止符。內容從開頭定界符的行終止符之後的第一個字元開始。結束定界符也是(""")。內容在結束定界符的第一個雙引號之前的最後一個字元處結束。

與字串文字中的字元不同,文字塊的內容中可以直接包含雙引號字元。當然也允許在文字塊中使用\“,但不是必需也不建議用。與字串文字中的字元不同,內容可以直接包含行終止符。允許在文字塊中用\n,但不是必需也不建議用。

如文字塊:

line 1
line 2
line 3

就等效於字串文字:

"line 1\nline 2\nline 3\n"

或字串文字的串聯:

"line 1\n" +
"line 2\n" +
"line 3\n"

文字塊功能在JDK 13中作為預覽功能(JEP 355)被引入。這個功能在JDK 14中得到了更新。文字塊是使用3個引號分隔的多行字串。根據文字塊在原始碼中的出現形式,多餘的用於縮排的白字元會被去掉。相應的演算法會在每一行中去掉同樣數量的白字元,直到其中任意的一行遇到非白字元為止。每一行字串末尾的白字元會被自動去掉。

下面程式碼中的文字塊xml會被去掉前面的2個白字元的縮排。

String xml = """
  <root>
    <a>Hello</a>
    <b>
      <c>
        <d>World</d>
      </c>
    </b>
  </root>
  """;

縮排的去除是要考慮到作為結束分隔符的3個引號的位置的。如果把上面的文字塊改成下面程式碼的格式,則沒有縮排會被去掉。注意最後一行中3個引號的位置。去除的白字元的數量需要考慮到全部行中前導的白字元數量,包括最後一行。最終去除的白字元數量是這些行中前導白字元數量的最小值。

String xml2 = """
  <root>
    <a>Hello</a>
    <b>
      <c>
        <d>World</d>
      </c>
    </b>
  </root>
""";

在文字塊中同樣可以使用\n和\r這樣的跳脫字元。除了String本身支援的跳脫字元之外,文字塊還支援2個特殊的跳脫字元:

- \:阻止插入換行符。

- \s:表示一個空格。可以用來避免末尾的白字元被去掉。

由於\的使用,下面程式碼中的longString實際上只有一行。

String longString = """
    hello \
    world \
    goodbye
    """;

在下面的程式碼中,透過使用\s,每一行的長度都為10。

String names = """
    alex     \s
    bob      \s
    long name\s
    """;

3.4 HTML

使用原始字串語法:

String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, world</p>\n" +
              "    </body>\n" +
              "</html>\n";

使用文字塊文字塊語法:

String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;

3.5 SQL

原始的字串語法:

String query = "SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`\n" +
               "WHERE `CITY` = 'INDIANAPOLIS'\n" +
               "ORDER BY `EMP_ID`, `LAST_NAME`;\n";

文字塊語法:

String query = """
               SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`
               WHERE `CITY` = 'INDIANAPOLIS'
               ORDER BY `EMP_ID`, `LAST_NAME`;
               """;

3.6 多語言示例

原始字串語法:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("function hello() {\n" +
                         "    print('\"Hello, world\"');\n" +
                         "}\n" +
                         "\n" +
                         "hello();\n");

文字塊語法:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("""
                         function hello() {
                             print('"Hello, world"');
                         }
                         
                         hello();
                         """);

關注我,緊跟本系列專欄文章,咱們下篇再續!

作者簡介:魔都架構師,多家大廠後端一線研發經驗,在分散式系統設計、資料平臺架構和AI應用開發等領域都有豐富實踐經驗。

各大技術社群頭部專家博主。具有豐富的引領團隊經驗,深厚業務架構和解決方案的積累。

負責:

  • 中央/分銷預訂系統效能最佳化
  • 活動&券等營銷中臺建設
  • 交易平臺及資料中臺等架構和開發設計
  • 車聯網核心平臺-物聯網連線平臺、大資料平臺架構設計及最佳化
  • LLM Agent應用開發
  • 區塊鏈應用開發

目前主攻市級軟體專案設計、構建服務全社會的應用系統。

參考:

  • 程式設計嚴選網

本文由部落格一文多發平臺 OpenWrite 釋出!

相關文章