编译期间确定类型安全——泛型(Generics)
泛型是提供給Javac編譯器使用的。可以限定集合中輸入的類型,讓編譯器擋住原始程序的非法輸入,編譯器編譯帶類型說明的集合時會去掉“類型”信息,使程序運行效率不受影響,對于參數(shù)化的泛型類型,getClass()方法的返回值和原始類型完全一樣,由于編譯生成的字節(jié)碼會去掉泛型的類型信息,只要能跳過編譯器,就可以往某個泛型集合中加入其它類型的數(shù)據(jù),例如,用反射得到集合,再調(diào)用其add方法即可。
ArrayList<E>類定義和ArrayList<Integer>類引用中涉及如下術(shù)語:
整個稱為ArrayList<E>泛型類型,ArrayList<E>中的E稱為類型變量或類型參數(shù),整個ArrayList<Integer>稱為參數(shù)化的類型,ArrayList<Integer>中的Integer稱為類型參數(shù)的實例或?qū)嶋H類型參數(shù),ArrayList<Integer>中的<>念著typeof,ArrayList稱為原始類型。
參數(shù)化類型與原始類型的兼容性:
參數(shù)化類型可以引用一個原始類型的對象,編譯報告警告,例如,
原始類型可以引用一個參數(shù)化類型的對象,編譯報告警告,例如,
Collection c = new Vector<String>();參數(shù)化類型不考慮類型參數(shù)的繼承關(guān)系:
Vector<String> v = new Vector<Object>() // 錯誤Vector<Object> v = new Vector<String>() // 也錯誤類型擦除
正確理解泛型概念的首要前提是理解類型擦除(type erasure)。 Java中的泛型類似于C++中的模板,但是這種相似性僅限于表面,Java中的泛型基本上都是在編譯器這個層次來實現(xiàn)的。屬于編譯器執(zhí)行類型檢查和類型診斷,然后生成普通的非泛型的字節(jié)碼,也就是在生成的Java字節(jié)代碼中是不包含泛型中的類型信息的,使用泛型的時候加上的類型參數(shù),會被編譯器在編譯的時候去掉。這種實現(xiàn)技術(shù)稱為類型擦除。如在代碼中定義的List<Object>和List<String>等類型,在編譯之后都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來說是不可見的。Java編譯器會在編譯時盡可能的發(fā)現(xiàn)可能出錯的地方,但是仍然無法避免在運行時刻出現(xiàn)類型轉(zhuǎn)換異常的情況。
很多泛型的奇怪特性都與這個類型擦除的存在有關(guān),包括:
- 泛型類并沒有自己獨有的Class類對象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class;
- 靜態(tài)變量是被泛型類的所有實例所共享的。對于聲明為MyClass<T>的類,訪問其中的靜態(tài)變量的方法仍然是 MyClass.myStaticVar。不管是通過new MyClass<String>還是new MyClass<Integer>創(chuàng)建的對象,都是共享一個靜態(tài)變量。
- 泛型的類型參數(shù)不能用在Java異常處理的catch語句中。因為異常處理是由JVM在運行時刻來進行的。由于類型信息被擦除,JVM是無法區(qū)分兩個異常類型MyException<String>和MyException<Integer>的。對于JVM來說,它們都是MyException類型的。也就無法執(zhí)行與異常對應的catch語句。
實例分析
了解了類型擦除機制之后,就會明白編譯器承擔了全部的類型檢查工作。編譯器禁止某些泛型的使用方式,正是為了確保類型的安全性。以上面提到的List<Object>和List<String>為例來具體分析:
public void inspect(List<Object> list) { for (Object obj : list) { System.out.println(obj); } list.add(1); //這個操作在當前方法的上下文是合法的。 }public void test() { List<String> strs = new ArrayList<String>(); inspect(strs); //編譯錯誤 }這段代碼中,inspect()方法接受List<Object>作為參數(shù),當在test方法中試圖傳入List<String>的時候,會出現(xiàn)編譯錯誤。假設這樣的做法是允許的,那么在inspect方法就可以通過list.add(1)來向集合中添加一個數(shù)字。這樣在test方法看來,其聲明為List<String>的集合中卻被添加了一個Integer類型的對象。這顯然是違反類型安全的原則的,在某個時候肯定會拋出ClassCastException。因此,編譯器禁止這樣的行為。編譯器會盡可能的檢查可能存在的類型安全問題。對于確定是違反相關(guān)原則的地方,會給出編譯錯誤。當編譯器無法判斷類型的使用是否正確的時候,會給出警告信息。?
通配符與上下界
在使用泛型類的時候,既可以指定一個具體的類型,如List<String>就聲明了具體的類型是String;也可以用通配符?來表示未知類型,如List<?>就聲明了List中包含的元素類型是未知的。 通配符所代表的其實是一組類型,但具體的類型是未知的。List<?>所聲明的就是所有類型都是可以的。但是List<?>并不等同于List<Object>。List<Object>實際上確定了List中包含的是Object及其子類,在使用的時候都可以通過Object來進行引用。而List<?>則其中所包含的元素類型是不確定。其中可能包含的是String,也可能是 Integer。如果它包含了String的話,往里面添加Integer類型的元素就是錯誤的。正因為類型未知,就不能通過new ArrayList<?>()的方法來創(chuàng)建一個新的ArrayList對象。因為編譯器無法知道具體的類型是什么。但是對于 List<?>中的元素確總是可以用Object來引用的,因為雖然類型未知,但肯定是Object及其子類。考慮下面的代碼:
public void wildcard(List<?> list) {list.add(1); //編譯錯誤 }如上所示,試圖對一個帶通配符的泛型類進行操作的時候,總是會出現(xiàn)編譯錯誤。其原因在于通配符所表示的類型是未知的。
因為對于List<?>中的元素只能用Object來引用,在有些情況下不是很方便。在這些情況下,可以使用上下界來限制未知類型的范圍。 如List<? extends Number>說明List中可能包含的元素類型是Number及其子類。而List<? super Number>則說明List中包含的是Number及其父類。當引入了上界之后,在使用類型的時候就可以使用上界類中定義的方法。比如訪問 List<? extends Number>的時候,就可以使用Number類的intValue等方法。
開發(fā)自己的泛型類
泛型類與一般的Java類基本相同,只是在類和接口定義上多出來了用<>聲明的類型參數(shù)。一個類可以有多個類型參數(shù),如 MyClass<X, Y, Z>。 每個類型參數(shù)在聲明的時候可以指定上界。所聲明的類型參數(shù)在Java類中可以像一般的類型一樣作為方法的參數(shù)和返回值,或是作為域和局部變量的類型。但是由于類型擦除機制,類型參數(shù)并不能用來創(chuàng)建對象或是作為靜態(tài)變量的類型。考慮下面的泛型類中的正確和錯誤的用法。
class ClassTest<X extends Number, Y, Z> { private X x; private static Y y; //編譯錯誤,不能用在靜態(tài)變量中 public X getFirst() {return x; } public void wrong() { Z z = new Z(); //編譯錯誤,不能創(chuàng)建對象 }}?
參考資料:
http://www.infoq.com/cn/articles/cf-java-generics
轉(zhuǎn)載于:https://www.cnblogs.com/shuaihua/archive/2013/01/17/2864509.html
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術(shù)人生總結(jié)
以上是生活随笔為你收集整理的编译期间确定类型安全——泛型(Generics)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对于拷贝构造函数和赋值构造函数的理解
- 下一篇: AbsoluteLayout(绝对布局)