一、String裏不再使用char[]
在JDK9之前,String內部是通過char數組(char[])來保存字符數據的。但在JDK9以後,String的實現內部改為使用byte數組(byte[])。這樣做的主要原因是為了節省內存空間,因為對於大量的拉丁文系列字符(如英文、數字、常見的標點符號等),使用byte數組存儲比使用char數組可以節省一半的空間。
同時,String類的內部還引入了一個名為coder的byte類型的字段。這個字段是用來標識存儲在byte數組中的數據是何種字符編碼的。在新的String類的實現中,存在兩種可能的字符編碼:ISO-8859-1(一個字符佔用一個字節)和UTF-16(一個字符佔用兩個字節)。對於ISO-8859-1編碼的字符串,coder的值為0,而對於UTF-16編碼的字符串,coder的值為1。這樣,通過檢查coder字段的值,就可以知道存儲在byte數組中的數據應該使用什麼樣的編碼方式進行處理,從而避免了因為字符編碼不同而導致的處理錯誤。
二、switch支持的類型不再侷限於基本類型與String
講這一點之前,首先要了解什麼是“模式匹配”,模式匹配是一種語言特性,用來檢查某一個值是否匹配某種模式,並根據結果執行相應的代碼,在Scala和Haskell中模式匹配是一項核心特性,而在Java中,模式匹配的概念在JDK14後被引入。
簡單而言,模式匹配可以讓你檢查一個變量或值的類型是否符合設定的的某些規則(模式),如果符合/不符合,就可以執行一些特定的操作。比如通過模式匹配,可以指定下面這樣的操作(JDK14):
Object obj = "hello";
if (obj instanceof String str) {
System.out.println(str.length());
}
在這個例子中,“String str”就是一個模式,同時完成了類型檢查(instanceof)和向下轉型(賦值給變量str),使得代碼更為簡潔。在JDK17中,switch也支持了這一功能:
Object obj = 10L;
switch (obj) {
case String str -> System.out.println("str: " + str);
case Integer intNum -> System.out.println("int: " + intNum);
case Long longNum -> System.out.println("long: " + longNum);
default -> throw new IllegalStateException("Unexpected value");
}
不過目前的模式匹配主要還是應用在類型檢查的時候自動轉換(略簡陋),在其他語言中,模式匹配還可以實現各種功能,比如在匹配的同時提取複雜數據結構中的值:
val list = List(1, 2, 3)
list match {
case head :: tail => println(s"head: $head, tail: $tail")
case Nil => println("empty list")
}
在這個例子中head :: tail就是一個模式,將隊列的頭尾分別放入head和tail對象中,從而執行下一步操作。
三、synchronized的偏向鎖已經被廢棄了
首先來回顧一下什麼是偏向鎖。偏向鎖是Java中synchronized關鍵字的一種優化手段,基本思想是同一個線程的反覆訪問無需加鎖,主要目標是消除數據在沒有競爭的情況下的同步操作,提高運行時性能。實際執行時,如果一個線程獲得了鎖,那麼鎖就進入偏向模式,此時記錄下線程ID,當這個線程再次請求鎖時,無需再做任何同步操作,這樣就省去了大量有關鎖申請的操作。
但是在真實情況中,偏向鎖並不總能帶來預期的性能優勢,相反地,在某些情況下(多核處理器環境),偏向鎖的撤銷需要進入全局安全點(即safepoint,虛擬機將所有的線程暫停執行),會帶來比較長的停頓時間。
偏向鎖想法是好的,但是增加了JVM的複雜性,同時也並沒有為所有應用都帶來性能提升。因此,在JDK15中,偏向鎖被默認關閉,在JDK18中,偏向鎖已經被徹底廢棄(無法通過命令行打開)。
四、G1推出後,分代回收的策略也發生了變化
這個大家相對比較熟悉,因為JDK7已經引入了G1垃圾收集器,在JDK9中被設置為默認的垃圾收集器。在G1中,沒有嚴格的年輕代和老年代的劃分,而是分為多個大小相同的獨立區域,每個區域在不同的時間點可能會扮演不同的角色。
五、不需要考慮JDK與JRE的關係了
JDK和JRE都是Java的重要組成部分,但它們的角色和用途是不同的。JDK是Java開發工具包,主要用於開發Java應用。它包含了JRE,同時還提供了一些額外的工具,如編譯器(javac)、調試器(jdb)等。JRE則是運行Java應用程序所需的環境。它包含了Java虛擬機(JVM)和Java類庫,也就是Java應用程序運行時所需的核心類和其他支持文件。
在JDK 8及之前的版本中,Oracle會提供獨立的JRE和JDK供用户下載。也就是説,你可以只安裝JRE來運行Java程序,也可以安裝JDK來開發Java程序。
然而從JDK 9開始,Oracle不再單獨發佈JRE。取而代之的是jlink工具,可以使用這個工具來生成定製的運行時鏡像。這種方式簡化了Java應用的部署,因你只需要分發包含你的應用和定製運行時鏡像的包,不需要單獨安裝JRE。
六、泛型可能不再是“語法糖”
提起Java的泛型,很多人第一時間想到的就是“語法糖”、“類型擦除”。類型擦除是Java泛型的一種實現機制。這意味着在編譯時,泛型類型會被替換為它的限定類型(如果沒有明確指定,那就是Object),並且在字節碼中並不保留泛型信息。
比如,如果你有一個如下的泛型類:
public class Box<T> {
private T object;
public void set(T object) { this.object = object; }
public T get() { return object; }
}
在編譯後,這個類會變成:
public class Box {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
類型擦除最大的優點就是保證了與老版本Java代碼的兼容性,因為在引入泛型之前的Java代碼都是沒有泛型信息的。但類型擦除畢竟是不得已為之,會有一些缺點,比如無法執行某些類型檢查、導致方法簽名衝突等。
接着, Valhalla項目出現了,Valhalla項目是OpenJDK的一個長期項目,它的主要目標是為Java引入一些改進和新特性,包括泛型的專門化。目前Java的泛型實現使用了類型擦除,這意味着泛型信息只存在於編譯時,而在運行時則被擦除。泛型的專門化意味着泛型信息可以保留到運行時,從而可以根據類型參數生成特定的代碼,提高運行效率,並且實現更安全的類型。
不過Valhalla項目的步子邁的有點大,進展比較慢,有很多人説這個項目“涼了”。儘管如此,很多新特性已經在預覽版本實現了,未來就會在正式版中出現。
七、Java可以在接口中定義私有方法
Java中的接口的目的是定義公開的API,而不是實現方法細節,所以在JDK8以前都不支持默認和靜態方法。但是出於便捷性的考慮,JDK8中支持方法的默認實現,這樣當一個接口有大量實現類的情況下,可以在不破壞原有實現的前提下迭代API。
JDK8中接口類的默認實現解決了模型抽象中的很多問題,隨之而來的是在接口中的默認方法如果需要共享一些代碼段,只能將這些代碼段抽象出一個新的函數。如果這個函數是靜態的(JDK8支持接口中定義靜態函數),那麼在函數裏無法訪問其他非靜態API;如果是一個有默認實現的API,就會導致實現類可以自由重載。而且最重要的是,不管怎麼樣,API都會被暴露出去,這不是開發者想要看到的。
最終,JDK9允許了開發者在接口中定義私有方法,從而提取和封裝默認方法中的公共代碼,減少代碼的冗餘。
作者 | 秋懷
原文鏈接
本文為阿里雲原創內容,未經允許不得轉載。