27.Android架構-泛型擦除機制

什麼是泛型擦除

Java的泛型是JDK5新引入的特性,爲了向下兼容,虛擬機其實是不支持泛型,所以Java實現的是一種
僞泛型機制,也就是說Java在編譯期擦除了所有的泛型信息,這樣Java就不需要產生新的類型到字節碼,
所有的泛型類型最終都是一種原始類型,在Java運行時根本就不存在泛型信息。

定義一個泛型接口

interface Plate<R>{
    void set(R r);
    R get();
}

使用ASM ByteCode Viewer查看他的字節碼


可以看到我們設置的泛型R,被擦除爲Object了,這就是泛型擦除

// class version 52.0 (52)
// access flags 0x600
// signature <R:Ljava/lang/Object;>Ljava/lang/Object;
// declaration: Plate<R>
abstract interface Plate {

  // compiled from: TestFanxing.java

  // access flags 0x401
  // signature (TR;)V
  // declaration: void set(R)
  public abstract set(Ljava/lang/Object;)V

  // access flags 0x401
  // signature ()TR;
  // declaration: R get()
  public abstract get()Ljava/lang/Object;
}

定義一個Plate1實現Plate接口

class Plate1<R> implements Plate<R>{
    private R r;
    @Override
    public void set(R r) {
        this.r = r;
    }
    @Override
    public R get() {
        return r;
    }
}

他的bytecode爲

class Plate1 implements Plate {
  private Ljava/lang/Object; r
  <init>()V
    ......  

  public set(Ljava/lang/Object;)V
    ...... 

  public get()Ljava/lang/Object;
    ......  

}

定義一個指定泛型類型的Plate2實現Plate接口

class Plate2<R extends Comparable<R>> implements Plate<R>{
    private R r;
    @Override
    public void set(R r) {
        this.r = r;
    }
    @Override
    public R get() {
        return r;
    }
}

可以看到我們限定了泛型的類型,那麼他的bytecode是什麼樣的?


class Plate2 implements Plate {

  private Ljava/lang/Comparable; r

  <init>()V
       ......
  public set(Ljava/lang/Comparable;)V
       ......
  public get()Ljava/lang/Comparable;
       ......
  public synthetic bridge get()Ljava/lang/Object;
       ......
  public synthetic bridge set(Ljava/lang/Object;)V
   L0
    LINENUMBER 43 L0
    ALOAD 0
    ALOAD 1
    CHECKCAST java/lang/Comparable
    INVOKEVIRTUAL Plate2.set (Ljava/lang/Comparable;)V
    RETURN
   L1
    LOCALVARIABLE this LPlate2; L0 L1 0
    // signature LPlate2<TR;>;
    // declaration: this extends Plate2<R>
    MAXSTACK = 2
    MAXLOCALS = 2
}

可以看到雖然我們在Plate2中只定義了一個set get方法,但是bytecode中卻有兩個,其中一個get set方法添加了synthetic bridge 表示這是一個橋接方法,作用是爲了保持多態性,可以看到CHECKCAST java/lang/Comparable,檢查類型是否爲Comparable,如果是的話再去調用上邊的public set(Ljava/lang/Comparable;)V方法。可以這樣理解,set(Ljava/lang/Object;)V是從Plate接口實現來的,set(Ljava/lang/Comparable;)V是他本身的,因爲限定了類型範圍

Java編譯器具體是如何擦除泛型的

  1. 檢查泛型類型,獲取目標類型
  2. 擦除類型變量,並替換爲限定類型
    如果泛型類型的類型變量沒有限定(<T>),則用Object作爲原始類型
    如果有限定(<T extends XClass>),則用XClass作爲原始類型
    如果有多個限定(T extends XClass1&XClass2),則使用第一個邊界XClass1作爲原始類
  3. 在必要時插入類型轉換以保持類型安全
  4. 生成橋方法以在擴展時保持多態性

泛型擦除的殘留

上邊我們是通過showbytecode的方式查看的字節碼,但是如果你點開類生成的class文件,你會發現,泛型既然被擦除了爲什麼在class中仍然可以看到?其實這裏看到的只是簽名而已,還保留了定義的格式,這樣對分析字節碼有好處。你甚至可以通過javap -c Plate2.class反編譯class,你會發現,R還是能被看到,我們要看bytecode,通過showbytecode的方式比較真實


泛型既然被擦除了,爲什麼通過反射還可以拿到泛型的類型?其實在類的常量池中保存了泛型信息



import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;

public class TestFanxing {
    Map<String, String> map;
    //擦除 其實在類常量池裏面保留了泛型信息
    public static void main(String[] args) throws Exception {
        Field f = TestFanxing.class.getDeclaredField("map");
        /**
         *  getType() 和 getGenericType()的區別 :
         * 1.首先是返回的類型不一樣,一個是Class對象一個是Type接口。
         * 2.如果屬性是一個泛型,從getType()只能得到這個屬性的接口類型。但從getGenericType()還能得到這個泛型的參數類型。
         * 3.getGenericType()如果當前屬性有簽名屬性類型就返回,否則就返回 Field.getType()。
         */
        System.out.println("f.getGenericType() "+f.getGenericType());                               // java.util.Map<java.lang.String, java.lang.String>
        System.out.println("f.getType() "+f.getType());                               // java.util.Map<java.lang.String, java.lang.String>
        //ParameterizedType是Type的子接口,表示一個有參數的類型,也就是包含泛型
        System.out.println(f.getGenericType() instanceof ParameterizedType);  // true
        ParameterizedType pType = (ParameterizedType) f.getGenericType();
        //getRawType(): 返回承載該泛型信息的對象, 如上面那個Map<String, String>承載範型信息的對象是Map
        System.out.println("pType.getRawType() "+pType.getRawType());                               // interface java.util.Map
        for (Type type : pType.getActualTypeArguments()) {
            // getActualTypeArguments(): 返回實際泛型類型列表, 如上面那個Map<String, String>實際範型列表中有兩個元素, 都是String
            System.out.println("pType.getActualTypeArguments() "+type);                                         // 打印兩遍: class java.lang.String
        }
        //Type getOwnerType(): 返回是誰的member.(上面那兩個最常用)
        System.out.println("pType.getOwnerType() "+pType.getOwnerType());                             // null
    }
}

使用泛型以及泛型擦除帶來的副作用

1. 泛型類型變量不能使用基本數據類型

比如沒有ArrayList<int>,只有ArrayList<Integer>.當類型擦除後,ArrayList的原始類中的類型變量(T)替換成Object,但Object類型不能 存放int值


2. 不能使用instanceof 運算符

因爲擦除後,ArrayList<String>只剩下原始類型,泛型信息String不存在了,所有沒法使用instanceof


3. 泛型在靜態方法和靜態類中的問題

因爲泛型類中的泛型參數的實例化是在定義泛型類型對象 (比如ArrayList<Integer>)的時候指定的,而靜態成員是不需要使用對象來調用的,所有對象都沒創建,如何確定這個泛型參數是什麼


4. 泛型類型中的方法衝突

因爲擦除後兩個equals方法變成一樣的了


5. 沒法創建泛型實例

因爲類型不確定

6. 沒有泛型數組

因爲數組是協變(在某些情況下,即使某個對象不是數組的基類型,我們也可以把它賦值給數組元素。這種屬性叫做協變(covariance)),擦除後就沒法滿足數組協變的原則

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章