首頁技術文章正文

JDK14有哪些新特性?JDK14新特性講解

更新時間:2023-03-21 來源:黑馬程序員 瀏覽量:

JDK 8 已經在 2014年 3月 18日正式可用,JDK 8作為長期支持(Long-Term-Support)版本,2018年09月25日作為下一個LTS的JDK版本:JDK 11也應運而生,Oracle表示會對JDK 11提供大力支持、長期支持。之后陸續(xù)發(fā)布了JDK 12 和JDK 13,JDK14也在2020年 3月17日正式發(fā)布,本節(jié)我們來針對JDK14的一些顯著新特性做講解,讓大家了解一下JDK 14的一些重要的新特性。

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

- 343: 打包工具 (Incubator)

- 345: G1的NUMA內存分配優(yōu)化

- 349: JFR事件流

- 352: 非原子性的字節(jié)緩沖區(qū)映射

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

JEP 305引入

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());
    }
}

這段程序做了3件事:

1. 先判斷obj的真實數據類型

2. 判斷成立后進行了強制類型轉換(將對象obj強制類型轉換為String)

3. 聲明一個新的本地變量str,指向上面的obj

這種模式的邏輯并不復雜,并且?guī)缀跛蠮ava程序員都可以理解。但是出于以下原因,上述做法并不是最理想的:

1. 語法臃腫乏味

2. 同時執(zhí)行類型檢測校驗和類型轉換。

3. String類型在程序中出現了3次,但是最終要的可能只是一個字符串類型的對象變量而已。

4. 重復的代碼過多,冗余度較高。

JDK 14提供了新的解決方案:新的instanceof模式匹配 ,新的模式匹配的用法如下所示,在`instanceof`的類型之后添加了變量`str`。如果`instanceof`對`obj`的類型檢查通過,`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()`方法的實現都會先檢查目標對象的類型。`instanceof`的模式匹配可以簡化`equals()`方法的實現邏輯。下面代碼中的Student類展示了相關的用法。

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);
    }

}

總結instanceof運算符“匹配”規(guī)則如下:

- 如果obj是String類型,則將obj類型轉換為String,并將其賦值給變量str。綁定的變量作用域為if語句內部,并不在false語句塊內。

- 到這兒,有一定Java基礎的同學應該看出來的JDK 14后的instanceof的模式匹配極大的簡化了類型檢查和轉型的問題。

JEP 361: Switch表達式 (標準)

JEP 361: Switch Expressions (Standard)

引入

擴展switch分支選擇語句的寫法。Switch表達式在經過JDK 12(JEP 325)和JDK(JEP 354)的預覽之后,在JDK 14中已經穩(wěn)定可用。

設計初衷

Java的switch語句是一個變化較大的語法(可能是因為Java的switch語句一直不夠強大、熟悉swift或者js語言的同學可與swift的switch語句對比一下,就會發(fā)現Java的switch相對較弱),因為Java的很多版本都在不斷地改進switch語句:JDK 12擴展了switch語句,使其可以用作語句或者表達式,并且傳統的和擴展的簡化版switch都可以使用。

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

下面簡單回顧一下switch的進化階段:

從Java 5+開始,Java的switch語句可使用枚舉了。

從Java 7+開始,Java的switch語句支持使用String類型的變量和表達式了。

從Java 11+開始,Java的switch語句會自動對省略break導致的貫穿提示警告(以前需要使用-X:fallthrough選項才能顯示出來) 。

但從JDK12開始,Java的switch語句有了很大程度的增強。

JDK 14的該JEP是從[JEP 325](https://openjdk.java.net/jeps/325)和[JEP 354](https://openjdk.java.net/jeps/354)演變而來的。但是,此JEP 361 Switch表達式 (標準)是獨立的,并且不依賴于這兩個JEP。

以前的switch程序

代碼如下:

public class Demo01{
    public static void main(String[] args){
        // 聲明變量score,并為其賦值為'C'
        var score = 'C';
        // 執(zhí)行switch分支語句
        switch (score) {
            case 'A':
                System.out.println("優(yōu)秀");
                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以前的switch寫法 ,這里不能忘記寫break,否則switch就會貫穿、導致程序出現錯誤(JDK 11會提示警告)。

JDK 14不需要break了

在JDK 12之前如果switch忘記寫break將導致貫穿,在JDK 12對switch的這一貫穿性做了改進。你只要將case后面的冒號(:)改成箭頭,那么你即使不寫break也不會貫穿了,因此上面程序可改寫如下形式:

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

上面代碼簡潔很多了。

JDK 14的switch表達式

JDK 12之后的switch甚至可作為表達式了——不再是單獨的語句。例如如下程序。

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

上面程序直接將switch表達式的值賦值給s變量,這樣switch不再是一個語句,而是一個表達式.

JDK 14中switch的多值匹配

當你把switch中的case后的冒號改為箭頭之后,此時switch就不會貫穿了,但在某些情況下,程序本來就希望貫穿比如我就希望兩個case共用一個執(zhí)行體!JDK 12之后的switch中的case也支持多值匹配,這樣程序就變得更加簡潔了。例如如下程序。

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

JDK 14的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語句。這是因為枚舉類型中的枚舉值可能發(fā)生變化。比如,枚舉類型Color 中原來只有3個值:RED、GREEN和BLUE。使用該枚舉類型的switch表達式窮盡了3種情況并完成編譯。之后Color中增加了一個新的值YELLOW,當用這個新的值調用之前的代碼時,由于不能匹配已有的值,編譯器產生的default會被調用,告知枚舉類型發(fā)生改變

JEP 368: 文本塊 (JDK 13后的第二個預覽版)

引入

在Java的開發(fā)過程中,通常需要進行大量字符串文字的拼接,等相關組織操作,從JDK 13到JDK 14開始文本塊新特性的提出,提高了Java程序書寫大段字符串文本的可讀性和方便性。

設計初衷

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

描述

文本塊是Java語言的新語法,可以用來表示任何字符串,具有更高的表達能力和更少的復雜度。文本塊的開頭定界符是由三個雙引號字符(""")組成的序列,后面跟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:表示一個空格??梢杂脕肀苊饽┪驳陌鬃址蝗サ簟?/p>

由于\的使用,下面代碼中的longString實際上只有一行。

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

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

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

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>
              """;

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`;
               """;

多語言示例

使用原始的字符串語法:

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();
                         """);


分享到:
在線咨詢 我要報名
和我們在線交談!