Java14:你需要知道的新特性
基本介紹
2020 年 3 月 17 日,JDK / Java 14 正式 GA(General Available)。這是自從 Java 採用六個月一次的釋出週期之後的第五次釋出。
此版本包含的 JEP Java/JDK Enhancement Proposals JDK 增強提案)比 Java 12 和 13 加起來的還要多。總共 16 個新特性,包括兩個孵化器模組 、三 個預覽特性、兩個棄用的功能以及兩個刪除的功能。
- 「孵化器模組」:將 尚未定稿的 API 和工具先交給開發者使用,以獲得反饋,並用這些反饋進一步改進 Java 平臺的質量。
- 「預覽特性」:是規格已經成型、實現已經確定,但還未最終定稿的功能。它們出現在 Java 中的目的是收集在真實世界中使用後的反饋資訊,促進這些功能的最終定稿 。這些特性可能會隨時改變,根據反饋結果, 這些特性甚至可能會被移除,但通常所有預覽特性最後都會在 Java 中固定下來。
環境準備
安裝 JDK 14 https://www.oracle.com/technetwork/java/javase/overview/index.html
安裝編譯器:IDEA 2020.1 或者 Eclipse 2020-03
新特性介紹
JEP 305:instanceof 的模式匹配(預覽特性)
這個特性很有意思,因為它為更為通用的模式匹配打開了大門。模式匹配通過更為簡便的語法基於一定的條件來抽取物件的元件,而 instanceof 剛好是這種情況,它先檢查物件型別,然後再呼叫物件的方法或訪問物件的欄位。
在 Java 14 之前,我們使用 instanceof 是這樣的
@Test public void test01() { Object obj = "hello java14"; if (obj instanceof String) { String str = (String) obj; System.out.println(str.contains("hello")); } else { System.out.println("不是 String 型別"); } }
在 Java 14 中是這樣的,相當於將上面的第4、5行程式碼簡化了,省略了強制轉化的過程,注意:「str 的作用域依舊是 if 結構內」
@Test public void test02() { Object obj = "hello java14"; if (obj instanceof String str) { System.out.println(str.contains("hello")); } else { System.out.println("不是 String 型別"); } }
有了該功能,可以減少 Java 程式中顯式強制轉換的數量,從而提高生產力,還能實現更精確、簡潔的型別安全的程式碼 。
JEP 358:非常實用的 NullPointerException
該特性改進了 NullPointerException 的可讀性,能更準確地給出 null 變數的資訊 。
就是這個老爺子當年發明的 null,讓成千上萬的程式設計師深惡痛絕,他稱自己犯了一個價值十億美金的錯誤,那就是空指標!
《Java 8 實戰》中是這樣說的:
- 它是錯誤之源。 NullPointerException 是目前 Java 程式開發中最典型的異常。它會使你的程式碼膨脹。
- 它讓你的程式碼充斥著深度巢狀的 null 檢查,程式碼的可讀性糟糕透頂。
- 它自身是毫無意義的。 null 自身沒有任何的語義, 尤其是 它代表的是在靜態型別語言中以一種錯誤的方式對缺失變數值的建模。
- 它破壞了 Java 的哲學。 Java 一直試圖避免讓程式設計師意識到指標的存在,唯一的例外是:null 指標。
- 它在 Java 的型別系統上開了個口子。 null 並不屬於任何型別,這意味著它可以被賦值給任意引用型別的變數。這會導致問題, 原因是當這個變數被傳遞到系統中的另一個部分後,你將無法獲知這個 null 變數最初賦值到底是什麼型別。
在 Java 8 中引入了 Optional,Optional 在可能為 null 的物件上做了一層封裝,強制你思考值不存在的情況,這樣
就能避免潛在的空指標異常 。
在 Java 14 中,對於 NPE 有了一個增強,該特性可以更好地提示哪個地方出現的空指標
需要我們在執行引數上加上 -XX:+ShowCodeDetailsInExceptionMessages
開啟此功能,這個增強特性不僅適用於方法呼叫,只要會導致 NullPointerException 的地方也都適用,包括欄位的訪問、陣列的訪問和賦值 。
JEP 359:Record (預覽特性)
我們有時候需要編寫許多低價值的重複程式碼來實現一個簡單的資料載體類:建構函式,訪問器,equals()、hashCode()、toString() 等。為了避免這種重複程式碼,Java 14 推出了 record 。
該預覽特性提供了一種更為緊湊的語法來宣告類。 值得一提的是,該特性可以大幅減少定義類似資料型別時所需的樣板程式碼。
使用 record 來減少類宣告語法,效果類似 lombok 的 @Data 註解, Kotlin 中的 data class 。它們的共同點是類的部分或全部狀態可以直接在類頭中描述,並且這個類中只包含了純資料而已。
我們宣告一個 record 型別的類
public record Person(String name, Integer age) {
}
它編譯後的 class 檔案如下
public final class Person extends java.lang.Record {
private final java.lang.String name;
private final java.lang.Integer age;
public Person(java.lang.String name, java.lang.Integer age) { /* 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 java.lang.Integer age() { /* compiled code */ }
}
呼叫其屬性的時候與普通類有所不同
@Test
public void test3(){
Person person = new Person("張三", 12);
person.name();
person.age();
}
當你用 record 宣告一個類時,該類將自動擁有以下功能:
- 獲取成員變數的簡單方法,以上面程式碼為例 name() 和 partner() 。注意區別於我們平常 getter 的寫法。
- 一個 equals 方法的實現,執行比較時會比較該類的所有成員屬性
- 重寫 equals 當然要重寫 hashCode
- 一個可以列印該類所有成員屬性的 toString 方法。
- 請注意只會有一個構造方法。
和列舉型別一樣,記錄也是類的一種受限形式。 作為回報,記錄物件在簡潔性方面提供了顯著的好處。
但是需要注意:
- 可以在 Record 宣告的類中定義靜態欄位、靜態方法、構造器或例項方法。
- 不能在 Record 宣告的類中定義例項欄位;類不能宣告為 abstract;不能宣告顯式的父類等。
JEP 361:switch 表示式
這是 JDK 12 和 JDK 13 中的預覽特性,現在是正式特性了。
我們使用 ->
來替代以前的 :
和 break
,另外還提供了 yield
來在 block 中返回值
程式碼演示:
public class SwitchExpression {
public static void main(String[] args) {
Season type = Season.AUTUMN;
String s = switch (type) {
case SPRING -> "春";
case SUMMER -> "夏";
case AUTUMN -> "秋";
case WINTER -> "冬";
default -> {
System.out.println("沒有" + type + "這個選項");
yield "error";
}
};
}
enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
}
JEP 368:文字塊(預覽第二版)
在 Java 中,通常需要使用 String 型別表達 HTML XML SQL 或 JSON 等格式的字串,在進行字串賦值時需要進行轉義和連線操作,然後才能編譯該程式碼,但這種表達方式難以閱讀並且難以維護。
於是在 JDK13 中引入了 text blocks,JDK 14 進行了第二輪 preview,JDK14 的版本主要增加了兩個功能,分別是\
和 \s
例如我們要寫一個 sql 語句,使用普通的字串是這樣的,這是比較簡單的語句,如果複雜一點,簡直不堪入目
@Test
public void test1() {
String sql =
"SELECT name, age\n" +
"FROM user\n" +
"WHERE age = 19";
System.out.println(sql);
}
使用了程式碼塊是這樣的,可讀性變高了
@Test
public void test2(){
String sql = """
SELECT name, age
FROM user
WHERE age = 19
""";
System.out.println(sql);
}
但是執行上述程式碼可以看出,程式碼塊裡寫的是什麼格式就會輸出什麼格式,如果我們想讓它只輸出一行,可讀性也高呢?
這時,我們可以使用 JDK 14 新加的 \
(取消換行)和 \s
(一個空格)
@Test
public void test3(){
String sql = """
SELECT name, age \
FROM user\s\
WHERE age = 19\s\
""";
System.out.println(sql);
}
JEP 366 :棄用 ParallelScavenge 和 SerialOld GC 組合
JDK 官方給出將這個 GC 組合標記為 Deprecate 的理由是:這個 GC 組合需要大量的程式碼維護工作,並且,這個 GC 組合很少被使用。因為它的使用場景應該是一個很大的 Young 區配合一個很小的 Old 區,這樣的話, Old 區用 SerialOldGC 去收集時停頓時間我們才能勉強接受 。
廢棄了 parallel young generation GC 與 SerialOld GC 的組合( XX:+UseParallelGC 與 XX: UseParallelOldGC 配合開啟 ),現在使用 -XX:+UseParallelGC -XX:-UseParallelOldGC
或者 -XX:-UseParallelOldGC
都會出現如下警告:
Java HotSpot(TM) 64 Bit Server VM warning: Option UseParallelOldGC was deprecated in version 14.0 and will likely be removed in a future release.
JEP 363 :刪除 CMS 垃圾回收器
該來的總會來,自從 G1 基於 Region 分代 )橫空 出世後, CMS 在 JDK9 中就被標記為 Deprecate 了( JEP 291: Deprecate the Concurrent Mark Sweep (CMS) Garbage Collector)
CMS 的弊端:
- 會產生記憶體碎片,導致併發清除後,使用者執行緒可用的空間不足 。
- 既然強調了併發( Concurrent),CMS 收集器 對 CPU 資源非常敏感。
- CMS 收集器無法處理浮動垃圾
上述的這些問題,尤其是碎片化問題,給你的 JVM 例項就像埋了一顆炸彈。說不定哪次就在你的業務高峰期來一次 FGC。當 CMS 停止工作時,會把 Serial Old GC 作為備選方案,而 Serial Old GC 是 JVM 中效能最差的垃圾回收方式,停頓個幾秒鐘,上十秒都有可能 。
移除了 CMS 垃圾收集器,如果在 JDK14 中使用 XX:+UseConcMarkSweepGC
的話,JVM 不會報錯,只是給出一個 warning 資訊。
JEP:ZGC on macOS and windows
先看一下 ZGC 的恐怖效能,它可以在儘可能對吞吐量影響不大的前提下,實現在任意堆記憶體大小下都可以把垃圾收集的停頓時間限制在十毫秒以內的低延遲。
JEP 364:ZGC 應用在 macOS 上、JEP 365:ZGC 應用在 Windows 上
JDK14 之前, ZGC 僅 Linux 才支援 。儘管許多使用 ZGC 的使用者都使用類 Linux 的環境,但在 Windows 和 macOS 上,人們也需要 ZGC 進行開發部署和測試。許多桌面應用也可以從 ZGC 中受益。因此, ZGC 特性被移植到了 Windows 和 macOS 上。
使用方式:-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
雖然 ZGC 還在試驗狀態,沒有完成所有特性,但此時效能已經相當亮眼,用「令人震驚、革命性」來形容,不為過。未來將在服務端、大記憶體、低延遲應用的首選垃圾收集器。
有幾個不重要的新特性沒有列舉,可自行檢視相關資料
本文大部分的資料來源於此視訊的課件:https://www.bilibili.com/video/BV1tC4y14