首先大家參考一下這篇文章
使用javap命令反編譯生成的GenTest類的class文件,可以得到下面的輸出:
我們清楚的看到,泛型T在GenTest類中就是Object類型( java.lang.Object value;)。同樣,get方法和set方法也都是將泛型T當作Object來處理的。如果我們規定泛型是Numeric類或者其子類,那么在這里泛型T就是被當作Numeric類來處理的。
好,既然GenTest類中沒有什么乾坤,那么我們繼續看使用GenTest的時候又什么新東西:
使用javap命令反編譯生成的GenTest類的class文件,可以得到下面的輸出:
重點在17、20和23三處。17就是調用getValue方法。而20則是關鍵——類型檢查。也就是說,在調用getValue方法之后,並沒有直接把返回值賦值給nv,而是先檢查了返回值是否是String類型,換句話說,“
String nv
=
test.getValue();”被編譯器變成了“
String nv
=
(String)test.getValue();”。最后,如果檢查無誤,在23處才會賦值。也就是說,如果沒有完成類型檢查,則會報出類似ClassCastException,而代碼將不會繼續向下執行,這就有效的避免了錯誤的出現。
也就是說:在類的內部,泛型類型就是被基類型代替的(默認是Object類型),而對外,所有返回值類型為泛型類型的方法,在真正使用返回值之前,都是會經過類型轉換的。
但是,一個頗具諷刺意味的問題出現了:如果允許了泛型數組,那么編譯器添加的強制類型轉換的代碼就會有可能是錯誤的。
看下面的例子:
上面的代碼中,最后一行是重點。根據本文第一部分的介紹,“ 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的泛型竟然陷入了這么一個怪圈。於是,他們在某個月黑風高的晚上,在某個猥瑣的會議室內,悄悄的決定一不做二不休——不支持泛型的數組了。(本段內容系作者猜測,並無任何事實根據,如有雷同,純粹巧合。)
http://www.blogjava.net/sean/archive/2005/08/09/9630.html
sean的這篇文章大部分是對的,但是到最后的結論部分“想想看,我們本來定義的是裝Map<Integer, String>的數組,結果我們卻可以往里面放任何Map,接下來如果有代碼試圖按原有的定義去取值,后果是什么不言自明。”,我覺得可以討論討論。
其實,sean的文中也提到,Java對泛型的支持其實就是在編譯器中做了做手腳,增加了一些強制類型轉換的代碼,也就是說原來需要我們手動寫的一些強制類型轉換的代碼,在泛型的世界里,Java編譯器就幫我們做了。
下面來一步步的分析泛型數組的問題:
Java中的泛型做了什么
首先看一下Java中的泛型做了什么。看下面這段代碼:public class GenTest<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 }
我們清楚的看到,泛型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<String> test = new GenTest<String>(); test.setValue(value); String nv = test.getValue(); } }
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 }
也就是說:在類的內部,泛型類型就是被基類型代替的(默認是Object類型),而對外,所有返回值類型為泛型類型的方法,在真正使用返回值之前,都是會經過類型轉換的。
為什么不支持泛型的數組?
根據上面的分析可以看出來,泛型其實是挺嚴謹的,說白了就是在“編譯的時候通過增加強制類型轉換的代碼,來避免用戶編寫出可能引發ClassCastException的代碼”。這其實也算是Java引入泛型的一個目的。但是,一個頗具諷刺意味的問題出現了:如果允許了泛型數組,那么編譯器添加的強制類型轉換的代碼就會有可能是錯誤的。
看下面的例子:
//下面的代碼使用了泛型的數組,是無法通過編譯的 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]; //上面兩行相當於使用數組移花接木,讓Java編譯器把GenTest<StringBuffer>當作了GenTest<String> 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的泛型竟然陷入了這么一個怪圈。於是,他們在某個月黑風高的晚上,在某個猥瑣的會議室內,悄悄的決定一不做二不休——不支持泛型的數組了。(本段內容系作者猜測,並無任何事實根據,如有雷同,純粹巧合。)