java ,不要太相信範型,範型失效的兩種情況
範型在java程式設計中有許多好處,也是我們平常經常使用的一種減少執行時錯誤的方式;但是在複雜的應用中,尤其是一個底層的模組中,我們不要太相信範型給我門反回的值。
首先:
Java中範型是編譯時檢查型別,實際在位元組碼檔案中沒有對範型的描述,如果中間有沒有範型的引用做橋樑或者執行時新增資料(通過java反射),那麼範型將會失去效用。下面的程式碼在debug時檢視會發現integers裡面儲存的資料時String型別,這個就是一個天坑。能夠執行且不會執行出錯。
先來驗證 確實沒有存在位元組碼中,我是通過javap -verbose class命令確認的
package flycat; import org.junit.Test; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.util.ArrayList; import java.util.List; /** * Unit test for simple App. */ public class AppTest { /** * Rigorous Test :-) */ @Test public void test() { List<Integer> ls = new ArrayList<>(); ls.add(4); } }
javap -verbose AppTest.class
public void test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=1 0: new #25 // class java/util/ArrayList 建立物件 3: dup 4: invokespecial #26 // Method java/util/ArrayList."<init>":()V 初始化物件 7: astore_1 8: aload_1 9: iconst_4 10: invokestatic #27 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;將4這個整型物件Integer執行static方法 13: invokeinterface #28, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 執行List的add方法 18: pop 19: return LineNumberTable: line 81: 0 line 82: 8 line 83: 19 LocalVariableTable: Start Length Slot Name Signature 0 20 0 this Lflycat/AppTest; 8 12 1 ls Ljava/util/List; LocalVariableTypeTable: Start Length Slot Name Signature 8 12 1 ls Ljava/util/List<Ljava/lang/Integer;>; RuntimeVisibleAnnotations: 0: #72()
上面部分程式碼看出,其中沒有看到泛型的影子,List<Integer> 和List是一樣的。
下面是“坑”的程式碼:
public void test() { List<String> strings = new ArrayList<String>(); strings.add("sdfsd"); List objects = strings; List<Integer> integers = (List<Integer>) objects; System.out.println(integers); }
執行結果是
[sdfsd]
這兒明顯是錯誤的,程式碼中泛型宣告是整數型別,但是最後是字串型別。
下面程式碼是一個通過執行時新增資料:
public void test()
{
List<Integer> strings = new ArrayList<Integer>();
strings.add(12);
Class clazz = null;
try {
clazz = Class.forName("java.util.ArrayList");
Method addMethod = clazz.getMethod("add", Object.class); //必須時Object,因為E範型在java.util.ArrayList上面的為Object
addMethod.invoke(strings, "asdfasd");
System.out.println(strings);
} catch (Exception e) {
e.printStackTrace();
}
}
執行結果是
[12, asdfasd]
這兒明顯是錯誤的,程式碼中泛型宣告是整數型別,但是最後是字串型別。
總結:
使用泛型造成錯誤一般都是書寫不規範造成的,而且都是在發生“掩蓋”的過程。
比如上上面的System.out.println(strings)這個程式碼 隱含著把所有資料轉換成String型別,原本的Integer型別轉換成String去了,String也會轉換成String不會發生錯誤,造成這種現象。
怎麼避免:
使用泛型的時候不要使用Object這種廣義的物件,要使用泛型對應的物件,這樣在執行過程中會對值進行Object轉泛型對應的型別,強轉失敗就會報錯。
ps:想要達到執行時新增資料造成範型約束失效,只有通過反射獲取Method物件執行。為什麼不能執行時通過使用者輸入資料新增資料物件讓範型失效,因為使用者輸入資料都是String型別,底層時char的,所有得到的int或者其它的時通過型別轉換得到的,其中沒有使用到範型,這兒新手容易混淆。