首先要提到的即是 Java 的自動裝箱(auto-boxing)和自動拆箱(auto-unboxing)。java
咱們知道,Java 語言擁有 8 個基本類型,每一個基本類型都有對應的包裝(wrapper)類型。緩存
之因此須要包裝類型,是由於許多 Java 核心類庫的 API 都是面向對象的。舉個例子,Java 核心類庫中的容器類,就只支持引用類型。app
當須要一個可以存儲數值的容器類時,咱們每每定義一個存儲包裝類對象的容器。spa
對於基本類型的數值來講,咱們須要先將其轉換爲對應的包裝類,再存入容器之中。在 Java 程序中,這個轉換能夠是顯式,也能夠是隱式的,後者正是 Java 中的自動裝箱。code
public int foo() { ArrayList<Integer> list = new ArrayList<>(); list.add(0); int result = list.get(0); return result; }
以上圖中的 Java 代碼爲例。我構造了一個 Integer 類型的 ArrayList,而且向其中添加一個 int 值 0。而後,我會獲取該 ArrayList 的第 0 個元素,並做爲 int 值返回給調用者。這段代碼對應的 Java 字節碼以下所示:對象
public int foo(); Code: 0: new java/util/ArrayList 3: dup 4: invokespecial java/util/ArrayList."<init>":()V 7: astore_1 8: aload_1 9: iconst_0 10: invokestatic java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 13: invokevirtual java/util/ArrayList.add:(Ljava/lang/Object;)Z 16: pop 17: aload_1 18: iconst_0 19: invokevirtual java/util/ArrayList.get:(I)Ljava/lang/Object; 22: checkcast java/lang/Integer 25: invokevirtual java/lang/Integer.intValue:()I 28: istore_2 29: iload_2 30: ireturn
當向泛型參數爲 Integer 的 ArrayList 添加 int 值時,便須要用到自動裝箱了。在上面字節碼偏移量爲 10 的指令中,咱們調用了 Integer.valueOf 方法,將 int 類型的值轉換爲 Integer 類型,再存儲至容器類中。blog
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
這是 Integer.valueOf 的源代碼。能夠看到,當請求的 int 值在某個範圍內時,咱們會返回緩存了的 Integer 對象;而當所請求的 int 值在範圍以外時,咱們則會新建一個 Integer 對象。繼承
在介紹反射的那一篇中,我曾經提到參數 java.lang.Integer.IntegerCache.high。這個參數將影響這裏面的 IntegerCache.high。ip
也就是說,咱們能夠經過配置該參數,擴大 Integer 緩存的範圍。Java 虛擬機參數 -XX:+AggressiveOpts 也會將 IntegerCache.high 調整至 20000。ci
奇怪的是,Java 並不支持對 IntegerCache.low 的更改,也就是說,對於小於 -128 的整數,咱們沒法直接使用由 Java 核心類庫所緩存的 Integer 對象。
當從泛型參數爲 Integer 的 ArrayList 取出元素時,咱們獲得的實際上也是 Integer 對象。若是應用程序期待的是一個 int 值,那麼就會發生自動拆箱。
在咱們的例子中,自動拆箱對應的是字節碼偏移量爲 25 的指令。該指令將調用 Integer.intValue 方法。這是一個實例方法,直接返回 Integer 對象所存儲的 int 值。
你可能已經留意到了,在前面例子生成的字節碼中,往 ArrayList 中添加元素的 add 方法,所接受的參數類型是 Object;而從 ArrayList 中獲取元素的 get 方法,其返回類型一樣也是 Object。
前者還好,可是對於後者,在字節碼中咱們須要進行向下轉換,將所返回的 Object 強制轉換爲 Integer,方能進行接下來的自動拆箱。
之因此會出現這種狀況,是由於 Java 泛型的類型擦除。這是個什麼概念呢?簡單地說,那即是 Java 程序裏的泛型信息,在 Java 虛擬機裏所有都丟失了。這麼作主要是爲了兼容引入泛型以前的代碼。
固然,並非每個泛型參數被擦除類型後都會變成 Object 類。對於限定了繼承類的泛型參數,通過類型擦除後,全部的泛型參數都將變成所限定的繼承類。也就是說,Java 編譯器將選取該泛型所能指代的全部類中層次最高的那個,做爲替換泛型的類。
class GenericTest<T extends Number> { T foo(T t) { return t; } }
舉個例子,在上面這段 Java 代碼中,我定義了一個 T extends Number 的泛型參數。它所對應的字節碼以下所示。能夠看到,foo 方法的方法描述符所接收參數的類型以及返回類型都爲 Number。方法描述符是 Java 虛擬機識別方法調用的目標方法的關鍵。
T foo(T); descriptor: (Ljava/lang/Number;)Ljava/lang/Number; flags: (0x0000) Code: stack=1, locals=2, args_size=2 0: aload_1 1: areturn Signature: (TT;)TT;
既然泛型會被類型擦除,那麼咱們還有必要用它嗎?
我認爲是有必要的。Java 編譯器能夠根據泛型參數判斷程序中的語法是否正確。舉例來講,儘管通過類型擦除後,ArrayList.add 方法所接收的參數是 Object 類型,可是往泛型參數爲 Integer 類型的 ArrayList 中添加字符串對象,Java 編譯器是會報錯的。
泛型的類型擦除帶來了很多問題。其中一個即是方法重寫。
今天我主要介紹了 Java 編譯器對幾個語法糖的處理。
基本類型和其包裝類型之間的自動轉換,也就是自動裝箱、自動拆箱,是經過加入 [Wrapper].valueOf(如 Integer.valueOf)以及 [Wrapper].[primitive]Value(如 Integer.intValue)方法調用來實現的。
Java 程序中的泛型信息會被擦除。具體來講,Java 編譯器將選取該泛型所能指代的全部類中層次最高的那個,做爲替換泛型的具體類。
因爲 Java 語義與 Java 字節碼中關於重寫的定義並不一致,所以 Java 編譯器會生成橋接方法做爲適配器。此外,我還介紹了 foreach 循環以及字符串 switch 的編譯。