基本介紹
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/BV1tC4y147US