本文描述 Java 8 的其餘新特性,包括提升的類型推斷,Java 類型注解,方法參數反射,針對存在鍵沖突的 HashMap 的性能改進,Date-Time 程式包,HotSpot 删除了 PermGen 。
提升的類型推斷
類型推斷這個概念可能比較陌生,但我們程式設計時或多或少都享受過它帶來的便利。
在 Java 7 中,可以使用指派語句的目标類型來進行類型推斷。
List<String> stringList = new ArrayList<>();
這是在使用泛型類執行個體時進行的類型推斷,隻要編譯器可以從上下文中推斷類型參數,就可以用一組空的類型參數(<>)替換調用泛型類的構造函數所需的類型參數。這對尖括号非正式地稱為鑽石符。
了解類型推斷需要明白一個目标類型和上下文的概念,目标類型是編譯器在表達式出現的地方(上下文)所期待的資料類型。例如此處的目标類型便是
ArrayList<String>
,參數化類型是 String,上下文在此處應該是指派語句。
在 JavaSE 8 中,可以在更多上下文中使用目标類型進行 類型推斷。最突出的例子是使用 方法調用的目标類型 來推斷其參數的資料類型。
List<String> stringList = new ArrayList<>();
stringList.add("A");
stringList.addAll(Arrays.asList());
此處 addAll 期待一個集合執行個體,在不考慮泛型的情況下,Arrays.asList 傳回一個 List 執行個體,它作為集合的子類,這是正常的。
在考慮泛型的情況下,此處的目标類型是 Collection<? extends String> ,Arrays.asList 傳回的是一個 List 執行個體。在這裡 Java 8 編譯器能夠根據目标類型推斷出 T 是 String。
Java 7 中,則無法從方法調用的參數目标類型中推斷出類型,上述代碼将在 Java 7 中得到一個錯誤:
error: no suitable method found for addAll(List<Object>) ...
method List.addAll(Collection<? extends String>) is not applicable (actual argument List<Object> cannot be converted to Collection<? extends String> by method invocation conversion)
是以,在這種Java編譯器無法推斷類型的情況下,必須用 類型見證器 顯式地指定類型變量的值。例如,以下在Java SE 7中将正常工作:
List<String> stringList = new ArrayList<>();
stringList.add("A");
stringList.addAll(Arrays.<String>asList());
Java 類型注解
Java 8 之前注解隻能出現在元素聲明的地方,而在 Java 8 注解可以出現在任何類型使用的地方,這類注解稱為類型注解。像這樣:
public static <@Schedule T> void start(T time) throws @Schedule Exception{
Job job = new @Schedule Job();
float i = ((@Schedule float) 123);
class TestImpl implements @Schedule Serializable{
}
}
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Schedules.class)
public @interface Schedule {
String time() default "";
}
表示注解能夠在哪裡使用的元注解是
Target
, 其接受一個
ElementType
類型的 value 元素數組。
Java 8 為其新增了兩種類型以支援類型注解,需要使用類型注解時,為期望注解的
Target
元注解添加
TYPE_USE
值即可:
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
TYPE_PARAMETER
表示可在聲明參數類型使用。
TYPE_USE
則表示可在類型出現的任何地方使用注解。如果仔細觀察其它類型,會發現它們的注釋均以
declaration
結尾,這也表明了在 Java 8 之前注解隻能應用于聲明。
類型注解的作用,官方文檔這樣描述道:
建立類型注解是為了支援對 Java 程式的改進分析,進而確定更強的類型檢查。JavaSE 8 發行版不提供類型檢查架構,但它允許您編寫(或下載下傳)一個類型檢查架構,該架構實作為與Java編譯器一起使用的一個或多個可插入子產品.
這有一個官方推薦的 檢查架構, 這個架構的簡介非常吸引人,它這樣描述道:“您是否厭倦了空指針異常、意外的副作用、SQL注入、并發錯誤、錯誤的相等性測試以及其他在測試期間或字段中出現的運作時錯誤?”。如果感興趣的話,可以深入研究。
方法參數反射
java.lang.reflect.Executable.getParameters 方法能夠從任何方法或者構造函數中擷取形式參數的名稱,但 .class 預設情況下并不存儲形式參數名稱,為了啟用它,需要在使用
javac
編譯時,使用
-parameters
配置選項。
抽象類有兩個子類,分别是
Executable
和
Method
類。
Constructor
public class Job {
public void testParametersName(String name, List<Integer> ages){
}
}
以上代碼使用
javac Job.java
編譯後,再使用
javap -v -s Job.class
反彙編後,可得到以下代碼(部分):
public void testParametersName(java.lang.String, java.util.List<java.lang.Integer>);
descriptor: (Ljava/lang/String;Ljava/util/List;)V
flags: ACC_PUBLIC
Code:
stack=0, locals=3, args_size=3
0: return
LineNumberTable:
line 14: 0
Signature: #13 // (Ljava/lang/String;Ljava/util/List<Ljava/lang/Integer;>;)V
如果使用攜帶 -parameters 選項編譯,再使用同樣的反彙編指令,針對以上代碼,我們可以得到如下:
public void testParametersName(java.lang.String, java.util.List<java.lang.Integer>);
descriptor: (Ljava/lang/String;Ljava/util/List;)V
flags: ACC_PUBLIC
Code:
stack=0, locals=3, args_size=3
0: return
LineNumberTable:
line 14: 0
MethodParameters:
Name Flags
name
ages
Signature: #17 // (Ljava/lang/String;Ljava/util/List<Ljava/lang/Integer;>;)V
對此,可以證明,方法參數名真實地存儲于 .class 檔案中。
針對存在鍵沖突的 HashMap 的性能改進
這指的是提高java.util.HashMap在高哈希沖突條件下,使用平衡樹而不是連結清單來存儲映射項,在LinkedHashMap類中實作相同的改進。
如果直接說紅黑樹,便是老生常談的一個話題。這裡簡單描述下其主要思想:“一旦
hash bucket
中的項數增長超過某個門檻值,該 bucket 将從使用條目的連結清單切換到平衡樹。在高哈希沖突的情況下,這将提高從O(n)到O(logn)的最壞情況性能”。
那麼,
Hashtable
,
WeakHashMap
以及
IdentityHashMap
有沒有實作這個改進呢?
使用
Hashtable
的遺留代碼可能依賴其疊代順序,是以,不會做此改進。
WeakHashMap
又由于必須考慮其弱密鑰的複雜性,這将導緻性能下降,這也是不允許的。
IdentityHashMap
使用系統識别碼來生成 hashcode,是以沖突通常很少發生,是以它并不需要實作此技術。
Date-Time 程式包
一組新的日期-時間程式包,提供新的日期-時間模型,這在
java.time
包中。常用的類應該是
LocalDateTime
,
LocalDate
以及
LocalTime
,使用起來非常友善。
HotSpot 删除了 PermGen
PermGen
被稱為永久代,也叫方法區。
Java 類在
Java Hotspot VM
中具有内部表示形式,被稱為類中繼資料。在
Java Hotspot VM
的早期版本中,類中繼資料是在永久代中生成配置設定的。在
JDK 8
中,永久代已删除,并且類中繼資料已配置設定在本機記憶體中。預設情況下,可用于類中繼資料的本地記憶體數量是無限的。使用該選項
MaxMetaspaceSize
對用于類中繼資料的本機記憶體量設定上限。
Java Hotspot VM
顯式管理用于中繼資料的空間。從作業系統請求空間,然後将其分成多個塊。類加載器從其塊配置設定中繼資料空間(塊綁定到特定的類加載器)。當為類加載器解除安裝類時,其塊将被回收以重新使用或傳回給OS。
需要明白的是,盡管永久代也常被稱作方法區,但永久代的移除和方法區并沒有關系。方法區是在 JVM 規範中定義的,不同的虛拟機選擇了不同的實作方式,這可能是最容易被誤解的地方了。
寫在最後
這是 Java 8 系列最後的一篇文章,此篇文章描述的内容都是淺嘗辄止。Java 8 增強的特性還有很多,但日常接觸的大多是這些了。
如果你覺得我的文章還不錯,并對後續文章感興趣的話,或者說我們有什麼能夠交流分享的,可以通過掃描下方二維碼來關注我的公衆号!