以下部分只是將我在學(xué)習(xí)筆記中的關(guān)于Java核心技術(shù)卷一 8.5 的筆記單拆出來凡恍,形成一篇文章山林。
8.5 泛型代碼和虛擬機(jī)
1.無論何時(shí)定義一個(gè)泛型類型房待,都自動提供了一個(gè)相應(yīng)的原始類型(raw type)。原始類型的名字就是刪去類型參數(shù)后的泛型類型名。擦除(erased) 類型變量, 并替換為限定類型(無限定的變量用 Object)桑孩。
2.java中的泛型只在程序源碼中存在拜鹤,當(dāng)編譯成字節(jié)碼之后,就會變?yōu)樵碱愋土鹘罚琷ava實(shí)現(xiàn)泛型的方法是類型擦除署惯。
3.當(dāng)程序調(diào)用泛型方法時(shí),如果擦除返回類型镣隶, 編譯器插入強(qiáng)制類型轉(zhuǎn)換极谊。例如,下面這個(gè)語句序列
Pair buddies = . .
Employee buddy = buddies.getFirst()安岂;
擦除
getFirst 的返回類型后將返回 Object 類型轻猖。編譯器自動插人 Employee
的強(qiáng)制類型轉(zhuǎn)換,將返回的Object類型轉(zhuǎn)換為Employee類型域那。當(dāng)存取一個(gè)泛型域時(shí)也要插入強(qiáng)制類型轉(zhuǎn)換咙边。假設(shè) Pair 類的 first
域和 second 域都是公 有的。表達(dá)式:
Employee buddy = buddies.first;
就會在結(jié)果字節(jié)碼中插入強(qiáng)制類型轉(zhuǎn)換次员。
4.類型擦除也會出現(xiàn)在泛型方法中败许,由此產(chǎn)生一些問題:在一個(gè)子類中,如果對于父類的一個(gè)函數(shù)進(jìn)性了重載淑蔚,則在擦除之后就會破壞重載市殷。比如說:
class Datelnterval extends Pair
{
????public void setSecond(LocalDate second)
????{
????????if (second.compareTo(getFirstO) >= 0)
??????? super.setSecond(second);
????}
}
在被擦除之后,就會變?yōu)椋?/p>
class Datelnterval extends Pair
{
????public void setSecond(LocalDate second)
??? {……}
}
而在父類中刹衫,則有一個(gè)public
void setSecond(Object second)方法醋寝,原本這個(gè)方法應(yīng)該作為模板方法被實(shí)例化為public void
setSecond(LocalDate
second)而后被子類中的同名方法所重載,但是此時(shí)由于擦除带迟,重載被破壞了音羞,父類的方法與子類中的這個(gè)方法變?yōu)榱藘蓚€(gè)方法,public void
setSecond(Object second)和public void setSecond(LocalDate
second)共同出現(xiàn)在子類中仓犬。
因此嗅绰,編譯器的解決方法是在作為子類的Datelnterval類中,生成一個(gè)橋方法搀继,即為:
public
void setSecond(Object second) { setSecond((Date) second);
}窘面,這個(gè)方法使用了與父類方法在擦除之后相同的函數(shù)名,返回值律歼,與參數(shù)列表民镜,從而完全將父類方法覆蓋啡专,而這個(gè)橋方法的作用則是险毁,將變量強(qiáng)制類型轉(zhuǎn)換之后,傳入子類方法,而在實(shí)際使用時(shí)畔况,如果一個(gè)Pair的引用引用了一個(gè)Datelnterval類的變量鲸鹦,在接收參數(shù)時(shí),即使調(diào)用橋函數(shù)跷跪,也會和以前一樣運(yùn)行馋嗜。
5.假設(shè)Datelnterval類也重載了getSecond()方法,比如說:
class Datelnterval extends Pair
{
??? public LocalDate getSecond()
??? {
???? return (Date) super.getSecond().clone();
????}
????……
}
那么吵瞻,在擦除之后葛菇,這個(gè)類中就會有兩個(gè)函數(shù):
public LocalDate getSecond()
public Object getSecond()/*這是一個(gè)橋方法,它將Pair類中的方法給覆蓋掉了*/
由于虛擬機(jī)中橡羞,是使用參數(shù)類型和返回類型唯一確定一個(gè)方法眯停,因此雖然在源碼中無法寫出如此形式,但是在最后生成的字節(jié)碼中卿泽,卻會出現(xiàn)以上這種情況莺债。
6.這也導(dǎo)致了其他問題,比如說:
public class TestTheBug
{
public static void method (Pair pairex)? {? ? ? ? System.out.println("Pair string pairex");? ? }
public static void method (Pair pairex)? {? ? ? ? System.out.println("Pair int pairex");? ? }
}
這段代碼表面上沒有問題签夭,但事實(shí)上是無法進(jìn)行編譯的齐邦,因?yàn)樵谶M(jìn)性擦除之后,這兩個(gè)函數(shù)的函數(shù)頭部已經(jīng)完全一致了第租,由此造成錯(cuò)誤措拇。但是,如果你進(jìn)行這樣的修改:
public class TestTheBug
{
public static string method (Pair pairex)
{
System.out.println("Pair string pairex");
? ? return "? "
??? }
public static int method (Pair pairex)
{
System.out.println("Pair int pairex");
??? return 1;
??? }
}
這兩個(gè)方法就可以照常運(yùn)行慎宾,因?yàn)橥ㄟ^不同的參數(shù)列表儡羔,通過了編譯器,而后又通過不同的返回值璧诵,使得虛擬機(jī)也可以區(qū)分汰蜘。
7.橋方法不僅用于泛型類型。 在一個(gè)方法覆蓋另一個(gè)方法時(shí)可以指定一個(gè)更嚴(yán)格的返回類型之宿。例如:
public class Employee implements Cloneable
??? {
??????? public Employee clone() throws CloneNotSupportedException { ...}
??? }
Object.clone 和 Employee.clone 方法被說成具有協(xié)變的返回類型(covariant returntypes)族操。 實(shí)際上,Employee 類有兩個(gè)克隆方法:
Employee clone() // defined above
Object clone() // 合成的橋方法比被,覆蓋了原本的Object.clone方法
合成的橋方法中調(diào)用了新定義的方法色难。