首先大家參考一下這篇文章
使用javap命令反編譯生成的GenTest類的class文件,可以得到下面的輸出:
我們清楚的看到,泛型T在GenTest類中就是Object類型(java.lang.Object value;)。同樣,get方法和set方法也都是將泛型T當(dāng)作Object來(lái)處理的。如果我們規(guī)定泛型是Numeric類或者其子類,那么在這里泛型T就是被當(dāng)作Numeric類來(lái)處理的。
好,既然GenTest類中沒(méi)有什么乾坤,那么我們繼續(xù)看使用GenTest的時(shí)候又什么新東西:
使用javap命令反編譯生成的GenTest類的class文件,可以得到下面的輸出:
重點(diǎn)在17、20和23三處。17就是調(diào)用getValue方法。而20則是關(guān)鍵——類型檢查。也就是說(shuō),在調(diào)用getValue方法之后,并沒(méi)有直接把返回值賦值給nv,而是先檢查了返回值是否是String類型,換句話說(shuō),“String nv = test.getValue();”被編譯器變成了“String nv = (String)test.getValue();”。最后,如果檢查無(wú)誤,在23處才會(huì)賦值。也就是說(shuō),如果沒(méi)有完成類型檢查,則會(huì)報(bào)出類似ClassCastException,而代碼將不會(huì)繼續(xù)向下執(zhí)行,這就有效的避免了錯(cuò)誤的出現(xiàn)。
也就是說(shuō):在類的內(nèi)部,泛型類型就是被基類型代替的(默認(rèn)是Object類型),而對(duì)外,所有返回值類型為泛型類型的方法,在真正使用返回值之前,都是會(huì)經(jīng)過(guò)類型轉(zhuǎn)換的。
但是,一個(gè)頗具諷刺意味的問(wèn)題出現(xiàn)了:如果允許了泛型數(shù)組,那么編譯器添加的強(qiáng)制類型轉(zhuǎn)換的代碼就會(huì)有可能是錯(cuò)誤的。
看下面的例子:
上面的代碼中,最后一行是重點(diǎn)。根據(jù)本文第一部分的介紹,“String value = ref.getValue()”會(huì)被替換成“String value = (String)ref.getValue()”。當(dāng)然我們知道,ref實(shí)際上是指向一個(gè)存儲(chǔ)著StringBuffer對(duì)象的GenTest對(duì)象。所以,編譯器生成出來(lái)的代碼是隱含著錯(cuò)誤的,在運(yùn)的時(shí)候就會(huì)拋出ClassCastException。
但是,如果沒(méi)有“String value = ref.getValue();”這行代碼,那么程序可以說(shuō)沒(méi)有任何錯(cuò)誤。這全都是Java中多態(tài)的功勞。我們來(lái)分析一下,對(duì)于上面代碼中創(chuàng)建出來(lái)的GenTest對(duì)象,其實(shí)無(wú)論value引用實(shí)際指向的是什么對(duì)象,對(duì)于類中的代碼來(lái)說(shuō)都是沒(méi)有任何影響的——因?yàn)樵贕enTest類中,這個(gè)對(duì)象僅僅會(huì)被當(dāng)作是基類型的對(duì)象(在這里也就是Object的對(duì)象)來(lái)使用。所以,無(wú)論是String的對(duì)象,還是StringBuffer的對(duì)象,都不可能引發(fā)任何問(wèn)題。舉例來(lái)說(shuō),如果調(diào)用valued的hashcode方法,那么,如果value指向的是String的對(duì)象,實(shí)際執(zhí)行的就是String類中的hashcode方法,如果是StringBuffer的對(duì)象,那么實(shí)際執(zhí)行的就是StringBuffer類中的hashcode方法。
從這里可以看出,即使支持泛型數(shù)組也不會(huì)帶來(lái)什么災(zāi)難性的后果,最多就是可能引發(fā)ClassCastException。而且平心而論,這個(gè)還是程序員自己的錯(cuò)誤,實(shí)在算不得是Java編譯器的錯(cuò)誤。
但是從另一個(gè)角度看,這確實(shí)是個(gè)巨大的諷刺:泛型是為了消滅ClassCastException而出現(xiàn)的,但是在這個(gè)時(shí)候它自己卻引發(fā)了ClassCastException。咱們中國(guó)人把這個(gè)叫做搬起石頭砸自己的腳。
當(dāng)然制定JSR的那幫子人可能沒(méi)學(xué)過(guò)中文,但是他們肯定是發(fā)現(xiàn)了這個(gè)令他們糾結(jié)的問(wèn)題。被標(biāo)榜為Java 5重要feature的泛型竟然陷入了這么一個(gè)怪圈。于是,他們?cè)谀硞€(gè)月黑風(fēng)高的晚上,在某個(gè)猥瑣的會(huì)議室內(nèi),悄悄的決定一不做二不休——不支持泛型的數(shù)組了。(本段內(nèi)容系作者猜測(cè),并無(wú)任何事實(shí)根據(jù),如有雷同,純粹巧合。)
http://www.aygfsteel.com/sean/archive/2005/08/09/9630.html
sean的這篇文章大部分是對(duì)的,但是到最后的結(jié)論部分“想想看,我們本來(lái)定義的是裝Map<Integer, String>的數(shù)組,結(jié)果我們卻可以往里面放任何Map,接下來(lái)如果有代碼試圖按原有的定義去取值,后果是什么不言自明。”,我覺(jué)得可以討論討論。
其實(shí),sean的文中也提到,Java對(duì)泛型的支持其實(shí)就是在編譯器中做了做手腳,增加了一些強(qiáng)制類型轉(zhuǎn)換的代碼,也就是說(shuō)原來(lái)需要我們手動(dòng)寫(xiě)的一些強(qiáng)制類型轉(zhuǎn)換的代碼,在泛型的世界里,Java編譯器就幫我們做了。
下面來(lái)一步步的分析泛型數(shù)組的問(wèn)題:
Java中的泛型做了什么
首先看一下Java中的泛型做了什么。看下面這段代碼:public class GenTest<T> {
T value;
public T getValue() {
return value;
}
public void setValue(T t) {
value = t;
}
}
T value;
public T getValue() {
return value;
}
public void setValue(T t) {
value = t;
}
}
使用javap命令反編譯生成的GenTest類的class文件,可以得到下面的輸出:
javap -c -p GenTest
Compiled from "GenTest.java"
public class GenTest extends java.lang.Object{
java.lang.Object value;
public GenTest();
Code:
0: aload_0
1: invokespecial #12; //Method java/lang/Object."<init>":()V
4: return
public java.lang.Object getValue();
Code:
0: aload_0
1: getfield #23; //Field value:Ljava/lang/Object;
4: areturn
public void setValue(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: putfield #23; //Field value:Ljava/lang/Object;
5: return
}
Compiled from "GenTest.java"
public class GenTest extends java.lang.Object{
java.lang.Object value;
public GenTest();
Code:
0: aload_0
1: invokespecial #12; //Method java/lang/Object."<init>":()V
4: return
public java.lang.Object getValue();
Code:
0: aload_0
1: getfield #23; //Field value:Ljava/lang/Object;
4: areturn
public void setValue(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: putfield #23; //Field value:Ljava/lang/Object;
5: return
}
我們清楚的看到,泛型T在GenTest類中就是Object類型(java.lang.Object value;)。同樣,get方法和set方法也都是將泛型T當(dāng)作Object來(lái)處理的。如果我們規(guī)定泛型是Numeric類或者其子類,那么在這里泛型T就是被當(dāng)作Numeric類來(lái)處理的。
好,既然GenTest類中沒(méi)有什么乾坤,那么我們繼續(xù)看使用GenTest的時(shí)候又什么新東西:
public class UseGenTest {
public static void main(String[] args) {
String value = "value";
GenTest<String> test = new GenTest<String>();
test.setValue(value);
String nv = test.getValue();
}
}
public static void main(String[] args) {
String value = "value";
GenTest<String> test = new GenTest<String>();
test.setValue(value);
String nv = test.getValue();
}
}
使用javap命令反編譯生成的GenTest類的class文件,可以得到下面的輸出:
D:\mymise\eclipse\workspace\Test\bin>javap -c -p UseGenTest
Compiled from "UseGenTest.java"
public class UseGenTest extends java.lang.Object{
public UseGenTest();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #16; //String value
2: astore_1
3: new #18; //class GenTest
6: dup
7: invokespecial #20; //Method GenTest."<init>":()V
10: astore_2
11: aload_2
12: aload_1
13: invokevirtual #21; //Method GenTest.setValue:(Ljava/lang/Object;)V
16: aload_2
17: invokevirtual #25; //Method GenTest.getValue:()Ljava/lang/Object;
20: checkcast #29; //class java/lang/String
23: astore_3
24: return
}
Compiled from "UseGenTest.java"
public class UseGenTest extends java.lang.Object{
public UseGenTest();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #16; //String value
2: astore_1
3: new #18; //class GenTest
6: dup
7: invokespecial #20; //Method GenTest."<init>":()V
10: astore_2
11: aload_2
12: aload_1
13: invokevirtual #21; //Method GenTest.setValue:(Ljava/lang/Object;)V
16: aload_2
17: invokevirtual #25; //Method GenTest.getValue:()Ljava/lang/Object;
20: checkcast #29; //class java/lang/String
23: astore_3
24: return
}
重點(diǎn)在17、20和23三處。17就是調(diào)用getValue方法。而20則是關(guān)鍵——類型檢查。也就是說(shuō),在調(diào)用getValue方法之后,并沒(méi)有直接把返回值賦值給nv,而是先檢查了返回值是否是String類型,換句話說(shuō),“String nv = test.getValue();”被編譯器變成了“String nv = (String)test.getValue();”。最后,如果檢查無(wú)誤,在23處才會(huì)賦值。也就是說(shuō),如果沒(méi)有完成類型檢查,則會(huì)報(bào)出類似ClassCastException,而代碼將不會(huì)繼續(xù)向下執(zhí)行,這就有效的避免了錯(cuò)誤的出現(xiàn)。
也就是說(shuō):在類的內(nèi)部,泛型類型就是被基類型代替的(默認(rèn)是Object類型),而對(duì)外,所有返回值類型為泛型類型的方法,在真正使用返回值之前,都是會(huì)經(jīng)過(guò)類型轉(zhuǎn)換的。
為什么不支持泛型的數(shù)組?
根據(jù)上面的分析可以看出來(lái),泛型其實(shí)是挺嚴(yán)謹(jǐn)?shù)模f(shuō)白了就是在“編譯的時(shí)候通過(guò)增加強(qiáng)制類型轉(zhuǎn)換的代碼,來(lái)避免用戶編寫(xiě)出可能引發(fā)ClassCastException的代碼”。這其實(shí)也算是Java引入泛型的一個(gè)目的。但是,一個(gè)頗具諷刺意味的問(wèn)題出現(xiàn)了:如果允許了泛型數(shù)組,那么編譯器添加的強(qiáng)制類型轉(zhuǎn)換的代碼就會(huì)有可能是錯(cuò)誤的。
看下面的例子:
//下面的代碼使用了泛型的數(shù)組,是無(wú)法通過(guò)編譯的
GenTest<String> genArr[] = new GenTest<String>[2];
Object[] test = genArr;
GenTest<StringBuffer> strBuf = new GenTest<StringBuffer>();
strBuf.setValue(new StringBuffer());
test[0] = strBuf;
GenTest<String> ref = genArr[0]; //上面兩行相當(dāng)于使用數(shù)組移花接木,讓Java編譯器把GenTest<StringBuffer>當(dāng)作了GenTest<String>
String value = ref.getValue();// 這里是重點(diǎn)!
GenTest<String> genArr[] = new GenTest<String>[2];
Object[] test = genArr;
GenTest<StringBuffer> strBuf = new GenTest<StringBuffer>();
strBuf.setValue(new StringBuffer());
test[0] = strBuf;
GenTest<String> ref = genArr[0]; //上面兩行相當(dāng)于使用數(shù)組移花接木,讓Java編譯器把GenTest<StringBuffer>當(dāng)作了GenTest<String>
String value = ref.getValue();// 這里是重點(diǎn)!
上面的代碼中,最后一行是重點(diǎn)。根據(jù)本文第一部分的介紹,“String value = ref.getValue()”會(huì)被替換成“String value = (String)ref.getValue()”。當(dāng)然我們知道,ref實(shí)際上是指向一個(gè)存儲(chǔ)著StringBuffer對(duì)象的GenTest對(duì)象。所以,編譯器生成出來(lái)的代碼是隱含著錯(cuò)誤的,在運(yùn)的時(shí)候就會(huì)拋出ClassCastException。
但是,如果沒(méi)有“String value = ref.getValue();”這行代碼,那么程序可以說(shuō)沒(méi)有任何錯(cuò)誤。這全都是Java中多態(tài)的功勞。我們來(lái)分析一下,對(duì)于上面代碼中創(chuàng)建出來(lái)的GenTest對(duì)象,其實(shí)無(wú)論value引用實(shí)際指向的是什么對(duì)象,對(duì)于類中的代碼來(lái)說(shuō)都是沒(méi)有任何影響的——因?yàn)樵贕enTest類中,這個(gè)對(duì)象僅僅會(huì)被當(dāng)作是基類型的對(duì)象(在這里也就是Object的對(duì)象)來(lái)使用。所以,無(wú)論是String的對(duì)象,還是StringBuffer的對(duì)象,都不可能引發(fā)任何問(wèn)題。舉例來(lái)說(shuō),如果調(diào)用valued的hashcode方法,那么,如果value指向的是String的對(duì)象,實(shí)際執(zhí)行的就是String類中的hashcode方法,如果是StringBuffer的對(duì)象,那么實(shí)際執(zhí)行的就是StringBuffer類中的hashcode方法。
從這里可以看出,即使支持泛型數(shù)組也不會(huì)帶來(lái)什么災(zāi)難性的后果,最多就是可能引發(fā)ClassCastException。而且平心而論,這個(gè)還是程序員自己的錯(cuò)誤,實(shí)在算不得是Java編譯器的錯(cuò)誤。
但是從另一個(gè)角度看,這確實(shí)是個(gè)巨大的諷刺:泛型是為了消滅ClassCastException而出現(xiàn)的,但是在這個(gè)時(shí)候它自己卻引發(fā)了ClassCastException。咱們中國(guó)人把這個(gè)叫做搬起石頭砸自己的腳。
當(dāng)然制定JSR的那幫子人可能沒(méi)學(xué)過(guò)中文,但是他們肯定是發(fā)現(xiàn)了這個(gè)令他們糾結(jié)的問(wèn)題。被標(biāo)榜為Java 5重要feature的泛型竟然陷入了這么一個(gè)怪圈。于是,他們?cè)谀硞€(gè)月黑風(fēng)高的晚上,在某個(gè)猥瑣的會(huì)議室內(nèi),悄悄的決定一不做二不休——不支持泛型的數(shù)組了。(本段內(nèi)容系作者猜測(cè),并無(wú)任何事實(shí)根據(jù),如有雷同,純粹巧合。)