首先大家參考一下這篇文章
sean的這篇文章大部分是對的,但是到最后的結論部分“想想看,我們本來定義的是裝Map的數組,結果我們卻可以往里面放任何Map,接下來如果有代碼試圖按原有的定義去取值,后果是什么不言自明。”,我覺得可以討論討論。
其實,sean的文中也提到,Java對泛型的支持其實就是在編譯器中做了做手腳,增加了一些強制類型轉換的代碼,也就是說原來需要我們手動寫的一些強制類型轉換的代碼,在泛型的世界里,Java編譯器就幫我們做了。
下面來一步步的分析泛型數組的問題:
Java中的泛型做了什么
首先看一下Java中的泛型做了什么。看下面這段代碼:public class GenTest {
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."":()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當作Object來處理的。如果我們規定泛型是Numeric類或者其子類,那么在這里泛型T就是被當作Numeric類來處理的。
好,既然GenTest類中沒有什么乾坤,那么我們繼續看使用GenTest的時候又什么新東西:public class UseGenTest {
public static void main(String[] args) {
String value = "value";
GenTest test = new GenTest();
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."":()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."":()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
}
重點在17、20和23三處。17就是調用getValue方法。而20則是關鍵——類型檢查。也就是說,在調用getValue方法之后,並沒有直接把返回值賦值給nv,而是先檢查了返回值是否是String類型,換句話說,“String nv = test.getValue();”被編譯器變成了“String nv =(String)test.getValue();”。最后,如果檢查無誤,在23處才會賦值。也就是說,如果沒有完成類型檢查,則會報出類似ClassCastException,而代碼將不會繼續向下執行,這就有效的避免了錯誤的出現。
也就是說:在類的內部,泛型類型就是被基類型代替的(默認是Object類型),而對外,所有返回值類型為泛型類型的方法,在真正使用返回值之前,都是會經過類型轉換的。
為什么不支持泛型的數組?
根據上面的分析可以看出來,泛型其實是挺嚴謹的,說白了就是在“編譯的時候通過增加強制類型轉換的代碼,來避免用戶編寫出可能引發ClassCastException的代碼”。這其實也算是Java引入泛型的一個目的。
但是,一個頗具諷刺意味的問題出現了:如果允許了泛型數組,那么編譯器添加的強制類型轉換的代碼就會有可能是錯誤的。
看下面的例子://下面的代碼使用了泛型的數組,是無法通過編譯的
GenTest genArr[] = new GenTest[2];
Object[] test = genArr;
GenTest strBuf = new GenTest();
strBuf.setValue(new StringBuffer());
test[0] = strBuf;
GenTest ref = genArr[0]; //上面兩行相當於使用數組移花接木,讓Java編譯器把GenTest當作了GenTest
String value = ref.getValue();// 這里是重點!
上面的代碼中,最后一行是重點。根據本文第一部分的介紹,“String value = ref.getValue()”會被替換成“String value =(String)ref.getValue()”。當然我們知道,ref實際上是指向一個存儲着StringBuffer對象的GenTest對象。所以,編譯器生成出來的代碼是隱含着錯誤的,在運的時候就會拋出ClassCastException。
但是,如果沒有“String value = ref.getValue();”這行代碼,那么程序可以說沒有任何錯誤。這全都是Java中多態的功勞。我們來分析一下,對於上面代碼中創建出來的GenTest對象,其實無論value引用實際指向的是什么對象,對於類中的代碼來說都是沒有任何影響的——因為在GenTest類中,這個對象僅僅會被當作是基類型的對象(在這里也就是Object的對象)來使用。所以,無論是String的對象,還是StringBuffer的對象,都不可能引發任何問題。舉例來說,如果調用valued的hashcode方法,那么,如果value指向的是String的對象,實際執行的就是String類中的hashcode方法,如果是StringBuffer的對象,那么實際執行的就是StringBuffer類中的hashcode方法。
從這里可以看出,即使支持泛型數組也不會帶來什么災難性的后果,最多就是可能引發ClassCastException。而且平心而論,這個還是程序員自己的錯誤,實在算不得是Java編譯器的錯誤。
但是從另一個角度看,這確實是個巨大的諷刺:泛型是為了消滅ClassCastException而出現的,但是在這個時候它自己卻引發了ClassCastException。咱們中國人把這個叫做搬起石頭砸自己的腳。
當然制定JSR的那幫子人可能沒學過中文,但是他們肯定是發現了這個令他們糾結的問題。被標榜為Java 5重要feature的泛型竟然陷入了這么一個怪圈。於是,他們在某個月黑風高的晚上,在某個猥瑣的會議室內,悄悄的決定一不做二不休——不支持泛型的數組了。(本段內容系作者猜測,並無任何事實根據,如有雷同,純粹巧合。)