Java 泛型(擦除,轉換,橋方法)

類型擦除

  • 編譯器在編譯期間所以的泛型信息都會被擦除

編譯 .java 生成的 .class (字節碼文件)中數據類型必須是確定好的。
如果一個 class 是泛型類,或者含有泛型方法,那麼編譯器在編譯時會將其中的類型變量去掉,生產一個與泛型類同名的原始類。在 原始類class文件 中的是其真正的類型(原始類型)。
注:
<>所修飾的部分(例:< T >)直接被擦除,而之後的用到的類型變量( T )會被原始類型代替。
原始類型:類型限界(無類型限界爲Object)

定義泛型類Generic1和Generic2

class Generic1<T> {
    T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

class Generic2<T extends A> {	// A爲類型限界
    T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

// 自定義的類
class A {
    String aName = "A";
}

class B extends A {
    String bName = "B";
}

Generic1和Generic2類分別經過類型擦除後生產的(原始類)字節碼如下

/*
 * 原始類型爲:Object
 */
class Generic1{
    Object value;

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }
}

/*
 * 原始類型爲:A
 */
class Generic2 {
    A value;

    public A getValue() {
        return value;
    }

    public void setValue(A value) {
        this.value = value;
    }
}

總結:
如果泛型類型的類型變量沒有限定() ,用Object作爲原始類型;
如果有限定(),用A作爲原始類型;
如果有多個限定(<T extends A1 & A2>),用第一個邊界的類型變量A1類作爲原始類型;

類型轉換

  • 當一個具有擦除返回類型的泛型方法被調用時會進行強制類型轉換

Generic1中的

    public T getValue() {
        return value;
    }

調用getValue方法時

        Generic1<String> g1 = new Generic1<>();
        g1.setValue("Hello world!");
        String s1 = g1.getValue();// 這裏編譯器編譯時處理成:String s1 = (String) g1.getValue()

由於原始類型是Object,返回的value的類型是Object,但是在調用這個方法的地方會根據類型變量進行強轉(做了一個checkcast()操作,即檢查< String >中的類型並強轉)
ArrayList中的

    E elementData(int index) {
        return (E) elementData[index];
    }
    
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);// 調用elementData方法
    }

調用get方法時

        ArrayList<Date> list = new ArrayList<Date>();
        list.add(new Date());
        Date myDate = list.get(0);

由於原始類型是Object,方法返回值是Object,但是在調用時會進行強制類型轉換。
以上需要注意的是:不是在方法裏強轉,是在調用的地方強轉

橋方法

  • 爲了避免類型變量擦除所帶來的多態問題

定義一個父類

class Generic<T> {
	T value;
	
	public void setValue(T value) {
		this.value = value;
	}
	
	public T getValue() {
		return value;
	}
}

定義一個子類,重寫父類方法

class MyGeneric extends Generic<String> {

	@Override
	public void setValue(String value) {
		super.setValue(value);
	}

	@Override
	public String getValue() {
		return super.getValue();
	}
}

子類重寫父類的方法,但是

// 繼承父類的已經類型擦除後的方法
public void setValue(Object value){...}
public T getValue(){...}
// 子類新增(重寫)的方法
public void setValue(String value){...}
public String getValue(){...}

由於參數類型不同這並不能算是重寫,爲了達到重寫的目的,編譯器使用橋方法解決。
編譯器在 MyGeneric 類中生成了兩個橋方法(這兩個橋方法會調用子類新增的方法)

    public void setValue(Object value) {
        setValue((String) value);// 子類新增的setValue(String value)方法
    }

    public Object getValue() {
        return getValue();// 子類新增的getValue()方法(返回的 String 類型的 getValue 方法)
    }

也就是說在重寫方法後的子類中通過編譯器會變成如下情形

class MyGeneric extends Generic<String> {

    public void setValue(String value){...} // 自己定義(重寫)的方法
    public void setValue(Object value){...} // 編譯器生成的橋方法

    public String getValue(){...} // 自己定義(重寫)的方法
    public Object getValue(){...} // 編譯器生成的橋方法
}

值得注意的是getValue方法:
編譯器允許在同一個類中出現方法簽名相同的多個方法嗎?
方法簽名(方法名+參數列表)用來確定一個方法;
人爲是不能在同一個類中編寫出方法簽名一樣的多個方法,否則編譯器會報錯;
但是,編譯器自己能夠創建出方法簽名一樣而返回類型不同的方法,JVM會用參數類型和返回類型來確定一個方法。

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