什麼是泛型擦除
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編譯器具體是如何擦除泛型的
- 檢查泛型類型,獲取目標類型
- 擦除類型變量,並替換爲限定類型
如果泛型類型的類型變量沒有限定(<T>),則用Object作爲原始類型
如果有限定(<T extends XClass>),則用XClass作爲原始類型
如果有多個限定(T extends XClass1&XClass2),則使用第一個邊界XClass1作爲原始類 - 在必要時插入類型轉換以保持類型安全
- 生成橋方法以在擴展時保持多態性
泛型擦除的殘留
上邊我們是通過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)
),擦除後就沒法滿足數組協變的原則