摘要:以上代碼編譯通過,運行通過引入泛型的同時,也為了兼容之前的類庫,開始引入的其實是偽泛型,在生成的字節(jié)碼中是不包含泛型中的類型信息的。進行類型擦除后,類型參數(shù)原始類型就是擦除去了泛型信息,最后在字節(jié)碼中的類型變量的真正類型。
Java泛型
Java泛型(generics)是JDK 5中引入的一個新特性,允許在定義類和接口的時候使用類型參數(shù)(type parameter)。聲明的類型參數(shù)在使用時用具體的類型來替換。泛型最主要的應用是在JDK 5中的新集合類框架中。泛型的引入可以解決JDK5之前的集合類框架在使用過程中較為容出現(xiàn)的運行時類型轉換異常,因為編譯器可以在編譯時通過類型檢查,規(guī)避掉一些潛在的風險。
在JDK5之前,使用集合框架時,是沒有類型信息的,統(tǒng)一使用Object,我找了一段JDK4 List接口的方法簽名
如下是JDK5開始引入泛型,List接口的改動,新的方法簽名,引入了類型參數(shù)。
boolean add(E e);
在JDK5之前,使用集合類時,可以往其中添加任意元素,因為其中的類型是Object,在取出的階段做強制轉換,由此可能引發(fā)很多意向不到的運行時強制轉換錯誤,比如以下代碼。
public class Test1 { public static void main(String[] args) { List a = new ArrayList(); a.add("123"); a.add(1); // 以上代碼可以正常通過編譯,其中同時含有了Integer類型和String類型 for (int i = 0 ; i < a.size(); i++) { int result = (Integer)a.get(i); // 在取出時需要對Object進行強制轉型 System.out.println(result); } } }
如上代碼就會在運行時階段帶來強轉異常,在編譯時間不能夠排查出潛在風險。
如果使用泛型機制,可以在編譯期間就檢查出List的類型插入的有問題,進行規(guī)避,如下代碼。
public class Test1 { public static void main(String[] args) { Lista = new ArrayList(); a.add("123"); // 編譯不通過 a.add(1); } }
引入泛型后,編譯器會在編譯時先根據(jù)類型參數(shù)進行類型檢查,杜絕掉一些潛在風險。
為何說是在編譯時檢查,因為在運行時仍然是可以通過反射,將不符合類型參數(shù)的數(shù)據(jù)插入至list中,如下代碼所示。
public class Test1 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Lista = new ArrayList(); List b = new ArrayList(); a.getClass().getMethod("add",Object.class).invoke(a,"abc"); // 以上代碼編譯通過,運行通過 } }
引入泛型的同時,也為了兼容JDK5之前的類庫,JDK5開始引入的其實是偽泛型,在生成的Java字節(jié)碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數(shù),會在編譯器在編譯的時候去掉。這個過程就稱為類型擦除。如在代碼中定義的List
Java的泛型機制和C++等的泛型機制實現(xiàn)不同,Java的泛型靠的還是類型擦除,目標代碼只會生成一份,犧牲的是運行速度。C++的模板會對針對不同的模板參數(shù)靜態(tài)實例化,目標代碼體積會稍大一些,運行速度會快很多。
進行類型擦除后,類型參數(shù)原始類型(raw type)就是擦除去了泛型信息,最后在字節(jié)碼中的類型變量的真正類型。無論何時定義一個泛型類型,相應的原始類型都會被自動地提供。類型變量被擦除,并使用其限定類型(無限定的變量用Object)替換。
class Pair{ private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } } Pair 的原始類型為: class Pair { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }
在Pair
在調(diào)用泛型方法的時候,可以指定泛型,也可以不指定泛型。在不指定泛型的情況下,泛型變量的類型為 該方法中的幾種類型的同一個父類的最小級,直到Object。在指定泛型的時候,該方法中的幾種類型必須是該泛型實例類型或者其子類。
public class Test1 { public static void main(String[] args) { /** 不指定泛型的時候 */ int i = Test1.add(1, 2); // 這兩個參數(shù)都是Integer,所以T為Integer類型 Number f = Test1.add(1, 1.2);// 這兩個參數(shù)一個是Integer,以風格是Float,所以取同一父類的最小級,為Number Object o = Test1.add(1, "asd");// 這兩個參數(shù)一個是Integer,以風格是Float,所以取同一父類的最小級,為Object /** 指定泛型的時候 */ int a = Test1.add(1, 2);// 指定了Integer,所以只能為Integer類型或者其子類 int b = Test1. add(1, 2.2);// 編譯錯誤,指定了Integer,不能為Float Number c = Test1. add(1, 2.2); // 指定為Number,所以可以為Integer和Float } // 這是一個簡單的泛型方法 public static T add(T x, T y) { return y; } }
因為類型擦除的問題,所有的泛型類型變量最后都會被替換為原始類型,但在泛型的使用中,我們不需要對取出的數(shù)據(jù)做強制轉換。
public class Test1 { public static void main(String[] args) { Lista = new ArrayList(); a.add(1); for (int i = 0 ; i < a.size(); i++) { int result = a.get(i); System.out.println(result); } } }
我們從字節(jié)碼的角度來探索一下。
public static void main(java.lang.String[]); Code: 0: new #2 // class java/util/ArrayList 3: dup 4: invokespecial #3 // Method java/util/ArrayList."":()V 7: astore_1 8: aload_1 9: iconst_1 10: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 13: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 18: pop 19: iconst_0 20: istore_2 21: iload_2 22: aload_1 23: invokeinterface #6, 1 // InterfaceMethod java/util/List.size:()I 28: if_icmpge 58 31: aload_1 32: iload_2 33: invokeinterface #7, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object; 38: checkcast #8 // class java/lang/Integer 這里JVM做了強轉 41: invokevirtual #9 // Method java/lang/Integer.intValue:()I 44: istore_3 45: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream; 48: iload_3 49: invokevirtual #11 // Method java/io/PrintStream.println:(I)V 52: iinc 2, 1 55: goto 21 58: return
在偏移量38的位置可以看到,JVM使用了checkcast指令,說明雖然在編譯時進行了類型擦除,但是JVM中仍然保留了類型參數(shù)的元信息,在取出時自動進行了強轉,這也算是使用泛型的方便之處吧。
在別人的例子有看到說類型擦除和多態(tài)的沖突,舉了一個例子。
public class Test1 { public static void main(String[] args) { DateInter dateInter = new DateInter(); dateInter.setValue(new Date()); dateInter.setValue(new Object());// 編譯錯誤 } } class Pair{ private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } } class DateInter extends Pair { @Override public Date getValue() { return super.getValue(); } @Override public void setValue(Date value) { super.setValue(value); } }
因為在類型擦除后,父類也就變成了一個普通的類,如下所示
class Pair { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }
但這樣setValue就從重寫變成了重載,顯然打破了想達到的目的,那么JVM是如何幫助解決這個沖突的呢?答案是 JVM幫我們搭了一個橋,具體我們從字節(jié)碼的角度再來看看。
class DateInter extends Pair{ DateInter(); Code: 0: aload_0 1: invokespecial #1 // Method Pair." ":()V 4: return public java.util.Date getValue(); Code: 0: aload_0 1: invokespecial #2 // Method Pair.getValue:()Ljava/lang/Object; 4: checkcast #3 // class java/util/Date 7: areturn public void setValue(java.util.Date); Code: 0: aload_0 1: aload_1 2: invokespecial #4 // Method Pair.setValue:(Ljava/lang/Object;)V 5: return public void setValue(java.lang.Object); Code: 0: aload_0 1: aload_1 2: checkcast #3 // class java/util/Date 5: invokevirtual #5 // Method setValue:(Ljava/util/Date;)V 8: return public java.lang.Object getValue(); Code: 0: aload_0 1: invokevirtual #6 // Method getValue:()Ljava/util/Date; 4: areturn }
從編譯的結果來看,我們本意重寫setValue和getValue方法的子類,有4個方法,最后的兩個方法,就是編譯器自己生成的橋接方法。可以看到橋方法的參數(shù)類型都是Object,也就是說,子類中真正覆蓋父類兩個方法的就是這兩個我們看不到的橋方法,打在我們自己定義的setvalue和getValue方法上面的@Oveerride只不過是假象。而橋方法的內(nèi)部實現(xiàn),就只是去調(diào)用我們自己重寫的那兩個方法。
所以,虛擬機巧妙的使用了巧方法,來解決了類型擦除和多態(tài)的沖突。
最后附上最近在瀏覽一些別人經(jīng)驗時得到一些tips。
使用JSON串反序列化對象集合時,記得標注對象的class類型,不然會得到一個只有原始類型也就是Object的集合,可能引起類型轉換錯誤,尤其是在服務調(diào)用的這種場景下。
重視編譯器提出的警告信息。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70683.html
摘要:接口也是集合中的一員,但它與接口有所不同,接口與接口主要用于存儲元素,而主要用于迭代訪問即遍歷中的元素,因此對象也被稱為迭代器。迭代器的實現(xiàn)原理我們在之前案例已經(jīng)完成了遍歷集合的整個過程。 【Collection、泛型】 主要內(nèi)容 Collection集合 迭代器 增強for 泛型 教學目標 [ ] 能夠說出集合與數(shù)組的區(qū)別 [ ] 說出Collection集合的常用功能 [ ]...
博客地址:Java泛型:類型擦除 前情回顧 Java泛型:泛型類、泛型接口和泛型方法 類型擦除 代碼片段一 Class c1 = new ArrayList().getClass(); Class c2 = new ArrayList().getClass(); System.out.println(c1 == c2); /* Output true */ 顯然在平時使用中,ArrayList...
摘要:靜態(tài)變量是被泛型類的所有實例所共享的。所以引用能完成泛型類型的檢查。對于這個類型系統(tǒng),有如下的一些規(guī)則相同類型參數(shù)的泛型類的關系取決于泛型類自身的繼承體系結構。事實上,泛型類擴展都不合法。 前言 和C++以模板來實現(xiàn)靜多態(tài)不同,Java基于運行時支持選擇了泛型,兩者的實現(xiàn)原理大相庭徑。C++可以支持基本類型作為模板參數(shù),Java卻只能接受類作為泛型參數(shù);Java可以在泛型類的方法中取得...
摘要:當某個類型變量只在整個參數(shù)列表的所有參數(shù)和返回值中的一處被應用了,那么根據(jù)調(diào)用方法時該處的實際應用類型來確定。即直接根據(jù)調(diào)用方法時傳遞的參數(shù)類型或返回值來決定泛型參數(shù)的類型。 標簽: java [TOC] 本文對泛型的基本知識進行較為全面的總結,并附上簡短的代碼實例,加深記憶。 泛型 將集合中的元素限定為一個特定的類型。 術語 ArrayList -- 泛型類型 ArrayLis...
閱讀 2289·2021-11-24 09:38
閱讀 1985·2021-11-22 14:44
閱讀 1150·2021-07-29 13:48
閱讀 2614·2019-08-29 13:20
閱讀 1115·2019-08-29 11:08
閱讀 2046·2019-08-26 10:58
閱讀 1264·2019-08-26 10:55
閱讀 3148·2019-08-26 10:39