天天看點

Java 8 新特性一覽

本文描述 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 增強的特性還有很多,但日常接觸的大多是這些了。

如果你覺得我的文章還不錯,并對後續文章感興趣的話,或者說我們有什麼能夠交流分享的,可以通過掃描下方二維碼來關注我的公衆号!

Java 8 新特性一覽