【高級進階】寫給大忙人看的JDK14新特性

更多學習資料,能夠關注下方微信公衆號,回覆關鍵字:Java高級資料 。 便可免費獲取完整的Java高級開發工程師視頻課程。

在這裏插入圖片描述
注意html

  • IDEA 2020.1以上版本才支持JDK14

從哪幾個方面學習新特性

  • 關注 語法層面(學習其餘語言的語法)
  • 關注 API層面(新增 / 過期 / 廢棄 )
  • 底層優化(JVM優化) 面試 / 瞭解底層原理

JDK版本使用者佔比

根據國外網站2019年統計,使用 Java 8 的開發者比重最大,其次就是Java 11。其餘版本的比重不大。這主要是因爲 Java 8 和 Java 11 是 LTS 版本。其餘的都是非 LTS 版本,意思就是其餘版本Oracle不會一直維護。
在這裏插入圖片描述java

版本更新時間

在這裏插入圖片描述

JDK14概述

2020年3月17日,Java 14 正式GA(General Available)。這是自從Java宣佈採用六個月一次的發佈週期後的第五次發佈。程序員

這次發佈的版本包含 16 個JEP(Java Enhancement Proposals,JDK加強提案)。包括兩個孵化器模塊丶三個預覽特性丶兩個廢棄功能丶兩個刪除功能。web

  • 孵化器模塊:將還沒有定稿的API和工具提早給開發者使用,以便得到反饋,並對反饋的信息整理分析後進一步改進Java的特性。
  • 預覽特性:功能已經成型丶實現已經肯定,可是尚未最終定稿的功能。爲了收集用戶使用後真實的反饋信息,將功能提供給用戶使用。促進這些功能的完善以便最終定稿。
    在這裏插入圖片描述

總特性概述

中文版本

JEP(Java 加強提案) 功能概述
305: instanceof的模式匹配(預覽)
343: 包裝工具(孵化器)
345: G1的NUMA感知內存分配
349: JFR事件流
352: 非易失性映射字節緩衝區
358: 有用的NullPointerExceptions
359: 記錄(預覽)
361: 開關表達式(標準)
362: 棄用Solaris和SPARC端口
363: 刪除併發標記掃描(CMS)垃圾收集器
364: Mac OS上的ZGC
365: Windows上的ZGC
366: 棄用ParallelScavenge + SerialOld GC組合
367: 刪除Pack200工具和API
368: 文本塊(第二預覽)
370: 外部存儲器訪問API(孵化器)

英文版本

JEP(Java 加強提案) 功能概述
305: Pattern Matching for instanceof (Preview)
343: Packaging Tool (Incubator)
345: NUMA-Aware Memory Allocation for G1
349: JFR Event Streaming
352: Non-Volatile Mapped Byte Buffers
358: Helpful NullPointerExceptions
359: Records (Preview)
361: Switch Expressions (Standard)
362: Deprecate the Solaris and SPARC Ports
363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector
364: ZGC on macOS
365: ZGC on Windows
366: Deprecate the ParallelScavenge + SerialOld GC Combination
367: Remove the Pack200 Tools and API
368: Text Blocks (Second Preview)
370: Foreign-Memory Access API (Incubator)

實用特性

JDK 14 的新特性中,對於語法層面的只有5個。因此下面將對這5個新特性進行詳細講解。面試

JEP(Java 加強提案) 功能概述
305: Pattern Matching for instanceof (Preview)
358: Helpful NullPointerExceptions
359: Records (Preview)
361: Switch Expressions (Standard)
368: Text Blocks (Second Preview)

JEP 305:instanceof的模式匹配(預覽)

這個特性目前在 JDK 14 中仍是預覽版本。經過爲開發人員提供 模式匹配 來加強Java編程語言instanceof。模式匹配使程序中的通用邏輯(如從對象中有條件的提取組件)更加簡潔安全。sql

幾乎全部程序都會包含一種特殊邏輯,就是先用表達式判斷是否知足某種條件。而後有條件的作進一步處理。如:instanceof表達式判斷。編程

Object obj = "string";
if (obj instanceof String) {
    String s = (String) obj;
    // 使用 s 對象
    System.out.println(s);
}

這一段代碼須要處理三件事情:安全

  • 判斷(str 是否爲String類型)
  • 轉換(將obj對象轉換爲String)
  • 定義一個新的局部變量 s ,而後咱們就可使用這個字符串。

JEP 305寫法微信

Object obj = "String";
if (obj instanceof String s) {
    // 使用 s 對象
    System.out.println(s);
} else {
    // 不可使用 s 對象
}

若是obj是一個String實例,那麼它被轉換爲String並分配給String類型變量 s 。綁定變量在if語句的true代碼塊中,而不是在if語句的false代碼塊中。架構

與局部變量的做用範圍不一樣,綁定變量的做用域由包含的表達式和語句的語義決定。例以下面的代碼中

Object obj = "String";
if (!(obj instanceof String s)) {
    // 報錯,不可使用 s 
    System.out.println(s.trim());
} else {
    // 可使用 s
    System.out.println(s.trim());
}

// 複雜的判斷條件寫法
Object obj = "String";
// 因爲obj instanceof String s條件成立,那麼這個if域就可使用s變量,因此&&條件能夠正常
if (obj instanceof String s && s.length() == 5) {
    System.out.println("obj對象是String類型,而且長度爲:5");
}

// 錯誤的判斷條件寫法
Object obj = "String";
// 若是obj instanceof String s條件不成立,那麼if域就不可使用s變量,s.length() == 5中不能夠繼續使用s變量。變量s不在||右側的範圍內。
if (obj instanceof String s || s.length() == 5) {
    System.out.println("obj對象是String類型,而且長度爲:5");
}

// 類型轉換和條件判斷,簡化代碼
public boolean strNotBlank(Object obj) {
    return obj instanceof String s && s.length() > 0;
}

JEP 358:有用的NullPointerExceptions

官方描述

  • 經過精確地描述哪一個變量是null來提升由JVM生成的NullPointerException信息的可用性。

目標

  • 向開發人員和支持人員提供有關程序提早終止的有用信息。
  • 經過更清晰地將動態異常與靜態程序代碼關聯起來,提升對程序的理解。
  • 減小新開發人員對NullPointerException的混淆和關注。

任何一個 Java 開發人員都會遇到NullPointerException(NPE)。因爲NPE幾乎會出如今程序中的任何位置,所以嘗試捕捉和恢復它們一般都不切實際。

即便開發人員依賴JVM查明NPE的實際來源。可是對於複雜的代碼,不適用調試器就沒法肯定那個變量爲空。假設下面的代碼出現一個NPE:

Object obj = null;
System.out.println(obj.toString().trim().contains(""));

JVM將打印出致使NPE的方法丶文件名和行號:

java.lang.NullPointerException
	at com.zhichunqiu.features.java14.Features02.test(Features02.java:16)

文件名稱和行號沒法精準的定位到那個變量丶方法爲空。objobj.toString()仍是obj.toString().trim()

若是JVM能夠提供所需的信息以查明NPE的來源,而後肯定其根本緣由,而無需使用額外的工具或改組代碼,則整個Java生態系統都將受益。自2006年以來,SAP的商業JVM就已經作到了這一點,得到了開發人員和支持工程師的一致好評。

JDK 14 優化JVM處理NPE

JVM NullPointerException在程序試圖取消引用引用的位置處拋出(NPE)null。經過分析程序的字節碼指令,JVM將精確地肯定哪一個變量是null,並在NPE 中用空細節消息描述該變量(根據源代碼)。而後,null-detail消息將顯示在JVM的消息中,以及方法,文件名和行號。

注意

在 JDK 14 中並無默認開啓Helpful NullPointerException特性。須要本身配置JVM參數,可能之後的版本會默認開啓。開啓參數:-XX:+ShowCodeDetailsInExceptionMessages
在這裏插入圖片描述
仍是用上面的代碼執行,看報錯效果:

Object obj = null;
System.out.println(obj.toString().trim().contains(""));

JVM 消息將剖析該語句並經過顯示致使如下內容的完整訪問路徑來查明緣由null

java.lang.NullPointerException: Cannot invoke "Object.toString()" because "obj" is null

	at com.zhichunqiu.features.java14.Features02.test(Features02.java:16)

JVM打印的消息除了說明NPE產生的類丶方法還說明了是obj爲空致使產生了NPE。

JEP 359:Records(預覽)

經過Records加強 Java編程語言。Records提供了一種緊湊的語法來聲明類,這些類都是淺拷貝不可變數據。

動機和目標

早在2019年2月份,Java語言架構師Brian Goetz曾經寫過一篇文章,詳細的說明並吐槽了Java語言,他和許多程序員同樣抱怨"Java太羅嗦太繁瑣"。他提到開發人員想要建立純數據載體類,一般都必須編寫大量低價值丶重複丶容易出錯的代碼。好比:構造函數丶getter / setter丶equals()丶hashCode()丶toString()等方法。

官方說明以下
在這裏插入圖片描述
描述

Records是 Java 語言中一種新的類型聲明。就像enum同樣,record是一種受限制的Class形式。record的引入得到了極大程度的語言簡潔性,可是也沒有了類的自由度:將API與表示分離的能力。

record是包含名稱和狀態描述的,這個狀態描述是聲明record組件的。record的主體是可選的。record的定義以下:

public record Features03(String name, int age) { }

隱藏特徵

因爲records的聲明很是簡單,因此records將會默認的得到不少標準的成員變量或方法。

  • 每個組成部分(變量 )都有一個隱藏的final字段聲明;

  • 每一個成員變量都有一個與變量名稱相同的獲取值的方法;例如:name屬性的獲取值方法不是instance.getName()而是instance.name()方法。這些都是編譯後默認生成的。

  • 一個public的全參構造方法,構造方法的參數和順序與類名稱後面的()中聲明的一致。

  • 實現了equalshashCode方法。若是兩個records類型的類具備相同的類型和相同的狀態,那麼他們必定是相等。(其實就是說明實現的equalshashCode方法是標準的)

  • 實現了toString方法,會打印類名稱和相關的屬性說明。(其實就是說明實現的toString方法是標準的)

反編譯後的Features03解讀

// 特色1:record定義的類,默認都繼承了Record,因爲Java是單繼承,因此record定義的類不容許在繼承其餘類。
// 錯誤示範:public record Features03 extends Object(String name, int age) { }
// 特色2:record都是final類,不容許被繼承。
public final class Features03 extends java.lang.Record {
    // 特色3:全部屬性都是final類型的
    private final java.lang.String name;
    private final int age;

    // 特色4:默認生成全參數構造函數
    public Features03(java.lang.String name, int age) { /* compiled code */ }

    // 特色5:默認生成toString hashCode equals方法
    public java.lang.String toString() { /* compiled code */ }

    public final int hashCode() { /* compiled code */ }

    public final boolean equals(java.lang.Object o) { /* compiled code */ }

    // 特色6:全部字段都提供getter方法,可是方法名稱和屬性名稱一致
    public java.lang.String name() { /* compiled code */ }

    public int age() { /* compiled code */ }
}

Records的限制

  • records不能繼承任何的其餘類,除了頭部定義的屬性(字段)外,聲明的任何其餘屬性都必須是static的。
  • records是隱式final類,不能夠是abstract抽象類。
  • records的組件是隱式final。所以能夠將records做爲一個數據的集合,由於不用擔憂數據會被修改。records只提供了屬性的getter方法,屬性都是final的賦值後沒法修改。
  • 除了以上的限制外,records的其餘行爲和正常的類是同樣的。具體以下
    • 能夠嵌套類丶聲明泛型丶實現接口丶經過new關鍵字實例化;
    • 能夠聲明靜態方法丶靜態字段丶靜態初始化容器丶構造函數丶實例方法和嵌套類型;
// 特徵1:定義泛型 / 實現接口
public record Features03<T>(String name, int age) implements Serializable {
    // 特徵2:定義的成員變量,必須是靜態的,能夠初始化值
    private static String address;
    // 定義公共靜態常量
    private static final String desc = null;

    //特徵3:實例方法
    public String getDesc() {
        return null;
    }

    //特徵4:靜態方法
    public static String getAddress() {
        return null;
    }

    // 特徵5:能夠自定義構造函數
    public Features03 {
        if (age > 10) {
            throw new IllegalArgumentException("年齡大於10歲");
        }
    }

    // 嵌套的records
    record MyFeatures(String dept, String desc) {

    }
}

Features03反編譯後的文件

public final class Features03 <T> extends java.lang.Record implements java.io.Serializable {
    private final java.lang.String name;
    private final int age;
    private static java.lang.String address;
    private static final java.lang.String desc;

    public Features03(java.lang.String name, int age) { /* compiled code */ }

    public java.lang.String getDesc() { /* compiled code */ }

    public static java.lang.String getAddress() { /* compiled code */ }

    public java.lang.String toString() { /* compiled code */ }

    public final int hashCode() { /* compiled code */ }

    public final boolean equals(java.lang.Object o) { /* compiled code */ }

    public java.lang.String name() { /* compiled code */ }

    public int age() { /* compiled code */ }

    static final class MyFeatures extends java.lang.Record {
        private final java.lang.String dept;
        private final java.lang.String desc;

        public MyFeatures(java.lang.String dept, java.lang.String desc) { /* compiled code */ }

        public java.lang.String toString() { /* compiled code */ }

        public final int hashCode() { /* compiled code */ }

        public final boolean equals(java.lang.Object o) { /* compiled code */ }

        public java.lang.String dept() { /* compiled code */ }

        public java.lang.String desc() { /* compiled code */ }
    }
}

JEP 361:Switch 表達式(標準)

switch表達式並非在 JDK 14纔出現的,早在 JDK 12(JEP 325) 和 13(JEP 354) 中switch就做爲預覽語言特性出現。JDK 12 中出現了 -> 操做符,JDK 13 中出現了yield關鍵字。而 JDK 14 是將12 / 13的預覽特性最終肯定下來做爲標準特性發布。

JDK 12 以前的寫法

public void test() {
    Week day = Week.FRIDAY;
    switch (day) {
        case MONDAY:
        case FRIDAY:
        case SUNDAY:
            System.out.println(6);
            break;
        case TUESDAY:
            System.out.println(7);
            break;
        case THURSDAY:
        case SATURDAY:
            System.out.println(8);
            break;
        case WEDNESDAY:
            System.out.println(9);
            break;
    }
}

// 將匹配條件後的值賦值給一個變量
public void test2() {
    int numLetters;
    Week day = Week.FRIDAY;
    switch (day) {
        case MONDAY:
        case FRIDAY:
        case SUNDAY:
            numLetters = 6;
            break;
        case TUESDAY:
            numLetters = 7;
            break;
        case THURSDAY:
        case SATURDAY:
            numLetters = 8;
            break;
        case WEDNESDAY:
            numLetters = 9;
            break;
    }
}
enum Week {
    MONDAY,
    FRIDAY,
    SUNDAY,
    TUESDAY,
    THURSDAY,
    SATURDAY,
    WEDNESDAY
}

JDK 12寫法

在 JDK 12 中引入一種新形式的開關標籤 」case L ->「,以表示若是標籤匹配則僅執行標籤右邊的代碼。並且還容許使用逗號分隔多個常量。

  • 」case L ->「開關標籤右側的代碼不只限於表達式,還能夠是代碼塊{}或者throw語句。

  • switch既然在 JDK 12 中的描述是表達式,那麼就說明能夠將結果賦值給一個變量。

public void test1() {
    Week day = Week.FRIDAY;
    switch (day) {
        case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
        case TUESDAY -> {
            // case 後面是一個代碼塊,包含多行代碼
            System.out.println("輸入的日期爲:" + TUESDAY);
            System.out.println(7);
        }
        case THURSDAY, SATURDAY -> System.out.println(8);
        case WEDNESDAY -> System.out.println(9);
        // case 後面拋出異常
        default -> throw new IllegalArgumentException("輸入的星期有誤");
    }
}

// 將匹配條件後的值賦值給一個變量,而後將結果輸出
// 即將結果賦值給一個變量,做爲表達式使用,switch須要以分號結尾
// 方式一:賦值變量後輸出結果
@Test
public void test3() {
    Week day = Week.FRIDAY;
    int numLetters = switch (day) {
        case MONDAY, FRIDAY, SUNDAY -> 6;
        case TUESDAY -> 7;
        case THURSDAY, SATURDAY -> 8;
        case WEDNESDAY -> 9;
        // case 後面拋出異常
        default -> throw new IllegalArgumentException("輸入的星期有誤");
    };// 末尾須要以分號結束,由於這個時候switch做爲表達式使用
    System.out.println(numLetters);
}
// 方式二:將switch做爲表達式直接輸出
public void test4() {
    Week day = Week.FRIDAY;
    System.out.println(
            // 將表達式結果輸出
            switch (day) {
                case MONDAY, FRIDAY, SUNDAY -> 6;
                case TUESDAY -> 7;
                case THURSDAY, SATURDAY -> 8;
                case WEDNESDAY -> 9;
                // case 後面拋出異常
                default -> throw new IllegalArgumentException("輸入的星期有誤");
            }
    );
}

enum Week {
    MONDAY,
    FRIDAY,
    SUNDAY,
    TUESDAY,
    THURSDAY,
    SATURDAY,
    WEDNESDAY
}

JDK 13 寫法

在 JDK 13 中爲了解決 JDK 12 中switch做爲表達式若是返回結果時候,case -> 右邊若是是一個代碼塊,那麼就不知道什麼時候返回結果。所以引入了一個關鍵字:yield

  • 若是使用case -> 的寫法,若是右邊只有一個表達式,就不須要使用yield關鍵字。只有代碼塊{}的時候才須要使用。
  • 若是使用case : 的寫法,那麼不管右邊如何,須要返回結果都必須使用yield
// yield關鍵字的用法,返回結果
// 方法一: case -> 寫法
public void test5() {
    Week day = Week.FRIDAY;
    // 將表達式結果輸出,
    int numLetters = switch (day) {
        case MONDAY, FRIDAY, SUNDAY -> 6;
        case TUESDAY -> 7;
        case THURSDAY, SATURDAY -> 8;
        // case後面若是是代碼塊,返回結果須要使用關鍵字:yield,不可使用return返回結果
        case WEDNESDAY -> {
            System.out.println("case -> 後面是代碼塊時候返回結果");
            yield 9;
        }
        // case 後面拋出異常
        default -> throw new IllegalArgumentException("輸入的星期有誤");
    };
}

// 方法二:case: 寫法
public void test6() {
    Week day = Week.FRIDAY;
    // 將表達式結果輸出
    int numLetters = switch (day) {
        case MONDAY, FRIDAY, SUNDAY: yield  6;
        case TUESDAY: yield 7;
        case THURSDAY, SATURDAY: yield 8;
        // case後面若是是代碼塊,返回結果須要使用關鍵字:yield,不可使用return返回結果
        case WEDNESDAY:  {
            System.out.println("case -> 後面是代碼塊時候返回結果");
            yield 9;
        }
        // case 後面拋出異常
        default:  throw new IllegalArgumentException("輸入的星期有誤");
    };
}

enum Week {
    MONDAY,
    FRIDAY,
    SUNDAY,
    TUESDAY,
    THURSDAY,
    SATURDAY,
    WEDNESDAY
}

警告

對於控制語句丶break丶yield丶return和continue,不能跳過switch表達式。

for (int i = 0; i < Integer.MAX_VALUE; ++i) {
    int k = switch (e) {
        case 0:
            yield 1;
        case 1:
            yield 2;
        default:
            // 錯誤的寫法,不能跳過switch表達式
            continue z;
    };
}

JEP 368:文本塊(第二次預覽)

一句話含義

文本塊是多行字符串文字,它能夠有效避免字符串特殊字符須要轉義序列。

JEP 目標

  • 經過簡化跨行源代碼的字符串工做,從而簡化了編寫Java程序的任務,同時避免了常見狀況下的轉義序列。
  • 加強Java程序中表示用非Java語言編寫的代碼字符串的可讀性。
  • 經過規定相同的轉義序列而且以字符串文字的形式操做,從而支持字符串文字的遷移。

病症所在

在 Java 中,在字符串文字中嵌入HTML丶XML丶SQL和JSON等片斷,一般須要先進行轉義和大量的串聯,而後才能編譯包含該片斷的代碼。一般這些代碼都是難以維護的。

所以,經過具備一種語言學機制,能夠將多行文字更直觀的表示字符串,從而跨越多行也不會出現轉義,從而提升了Java類程序的可讀性和可寫性。這就是文本塊誕生的緣由。

語法規則

文本塊的定義由開始定界符(三個雙引號字符)和結束定界符(三個雙引號字符)肯定文本塊的邊界String str = """ 文本內容 """。同時引入了\表示取消換行和\s表示一個空格。

文本塊示例

// 注意文本塊每一行末尾都有一個換行符,因此普通的字符串長度要多1個單位。
public void testBlock() {
    // 每一行字符串後面都有\n轉義,可讀性太差
    String html = "<html>\n" +
            "\t<body>\n" +
            "\t\t<p>Hello, world</p>\n" +
            "\t</body>\n" +
            "</html>";
    // 文本塊寫法,簡單清晰,可讀性好
    String htmlBlock = """
            <html>
            	<body>
            		<p>Hello, world</p>
            	</body>
            </html>
            """;
    System.out.println(html.length()); // 結果:53
    System.out.println(htmlBlock.length()); // 結果:54
}

語法示例

// 正確的定義文本塊
String sql = """ // 開始分隔符一行
        文本塊內容
        """; // 結束分隔符一行
    
// 文本塊中三個"字符的序列,至少須要轉義一個字符,以免和結束定界符衝突
String code =
        """
        String text = \"""
            A text block inside a text block
        \""";
        """;

// 錯誤語法示範
String a = """""";   // 錯誤,在開始分隔符後面沒有換行符
String b = """ """;  // 錯誤,在開始分隔符後面沒有換行符
String c = """
           ";        // 錯誤,並無結束分隔符
String d = """
           abc \ def
           """;      // 錯誤,斜杆沒有轉義

新的轉義序列

爲了更好的處理換行符和空格的處理,JDK 14 引入了兩個新的轉義序列。

  • \轉義序列明確禁止插入換行符 , 因爲字符文字和傳統字符串文字不容許嵌入換行符的緣由,\轉義序列僅適用於文本塊
  • 新的\s轉義序列僅轉換爲一個空格(\u0020),該\s轉義序列能夠在這兩個文本塊和傳統的字符串文字中。
// 對於一個很長的字符串內容,爲了更加方便查看,一般會拆分爲較小的子字符串進行拼接,而後將結果的字符串表達式分散到幾行中:
String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " +
        "elit, sed do eiusmod tempor incididunt ut labore " +
        "et dolore magna aliqua.";

// 使用\<line-terminator>轉義序列,能夠將其表示爲:
// \ 表示後面是沒有換行符的,因此字符串會是一行輸出
String text = """
                Lorem ipsum dolor sit amet, consectetur adipiscing \
                elit, sed do eiusmod tempor incididunt ut labore \
                et dolore magna aliqua.\
                """;
// \s在這個示例中,在每行的末尾使用能夠確保每行正好是六個字符長度
String colors = """
    red  \s
    green\s
    blue \s
    """;
    
// 能夠在任何使用字符串文字的地方使用文本塊。例如,文本塊和字符串文字能夠互換使用:
String code = "public void print(Object o) {" +
              """
                  System.out.println(Objects.toString(o));
              }
              """;
                  
// 不建議的作法:使用文本塊拼接,這樣會顯得很笨拙
String code = """
              public void print(""" + type + """
                                                 o) {
                  System.out.println(Objects.toString(o));
              }
              """;

API對文本塊的支持:附加方法

  • String::stripIndent():用於從文本塊內容中去除附帶的空白
  • String::translateEscapes():用於翻譯轉義序列
  • String::formatted(Object... args):簡化文本塊中的值替換
更多學習資料,能夠關注下方微信公衆號,回覆關鍵字:Java高級資料 。 便可免費獲取完整的Java高級開發工程師視頻課程。

在這裏插入圖片描述