前言
這里總結一下泛型中需要注意的一些地方和面試題,通過面試題可以讓你掌握的更清楚一些。
泛型相關問題
1、泛型類型引用傳遞問題
在Java中,像下面形式的引用傳遞是不允許的:
ArrayList<String> arrayList1=new ArrayList<Object>();//編譯錯誤 ArrayList<Object> arrayList1=new ArrayList<String>();//編譯錯誤
我們先看第一種情況,將第一種情況拓展成下面的形式:
實際上,在第4行代碼處,就會有編譯錯誤。那么,我們先假設它編譯沒錯。那么當我們使用arrayList2引用用get()方法取值的時候,返回的都是String類型的對象,可是它里面實際上已經被我們存放了Object類型的對象,這樣,就會有ClassCastException了。所以為了避免這種極易出現的錯誤,Java不允許進行這樣的引用傳遞。(這也是泛型出現的原因,就是為了解決類型轉換的問題,我們不能違背它的初衷)。
在看第二種情況,將第二種情況拓展成下面的形式:
ArrayList<String> arrayList1=new ArrayList<String>(); arrayList1.add(new String()); arrayList1.add(new String()); ArrayList<Object> arrayList2=arrayList1;//編譯錯誤
沒錯,這樣的情況比第一種情況好的多,最起碼,在我們用arrayList2取值的時候不會出現ClassCastException,因為是從String轉換為Object。可是,這樣做有什么意義呢,泛型出現的原因,就是為了解決類型轉換的問題。我們使用了泛型,到頭來,還是要自己強轉,違背了泛型設計的初衷。所以java不允許這么干。再說,你如果又用arrayList2往里面add()新的對象,那么到時候取得時候,我怎么知道我取出來的到底是String類型的,還是Object類型的呢?
所以,要格外注意泛型中引用傳遞問題。
2、泛型類型變量不能是基本數據類型
就比如,沒有ArrayList<double>,只有ArrayList<Double>。因為當類型擦除后,ArrayList的原始類中的類型變量(T)替換為Object,但Object類型不能存儲double值。
3、運行時類型查詢
因為類型擦除之后,ArrayList<String>只剩下原始類型,泛型信息String不存在了。
4、泛型在靜態方法和靜態類中的問題
泛型類中的靜態方法和靜態變量不可以使用泛型類所聲明的泛型類型參數
因為泛型類中的泛型參數的實例化是在定義泛型類型對象(例如ArrayList<Integer>)的時候指定的,而靜態變量和靜態方法不需要使用對象來調用。對象都沒有創建,如何確定這個泛型參數是何種類型,所以當然是錯誤的。
但是要注意區分下面的一種情況:
public class Test2<T> { public static <T >T show(T one){//這是正確的 return null; } }
因為這是一個泛型方法,在泛型方法中使用的T是自己在方法中定義的T,而不是泛型類中的T。
泛型相關面試題
1. Java中的泛型是什么 ? 使用泛型的好處是什么?
泛型是一種參數化類型的機制。它可以使得代碼適用於各種類型,從而編寫更加通用的代碼,例如集合框架。
泛型是一種編譯時類型確認機制。它提供了編譯期的類型安全,確保在泛型類型(通常為泛型集合)上只能使用正確類型的對象,避免了在運行時出現ClassCastException。
2、Java的泛型是如何工作的 ? 什么是類型擦除 ?
泛型的正常工作是依賴編譯器在編譯源碼的時候,先進行類型檢查,然后進行類型擦除並且在類型參數出現的地方插入強制轉換的相關指令實現的。
編譯器在編譯時擦除了所有類型相關的信息,所以在運行時不存在任何類型相關的信息。例如List<String>在運行時僅用一個List類型來表示。為什么要進行擦除呢?這是為了避免類型膨脹。
3. 什么是泛型中的限定通配符和非限定通配符 ?
限定通配符對類型進行了限制。有兩種限定通配符,一種是<? extends T>它通過確保類型必須是T的子類來設定類型的上界,另一種是<? super T>它通過確保類型必須是T的父類來設定類型的下界。泛型類型必須用限定內的類型來進行初始化,否則會導致編譯錯誤。另一方面<?>表示了非限定通配符,因為<?>可以用任意類型來替代。
4. List<? extends T>和List <? super T>之間有什么區別 ?
這和上一個面試題有聯系,有時面試官會用這個問題來評估你對泛型的理解,而不是直接問你什么是限定通配符和非限定通配符。這兩個List的聲明都是限定通配符的例子,List<? extends T>可以接受任何繼承自T的類型的List,而List<? super T>可以接受任何T的父類構成的List。例如List<? extends Number>可以接受List<Integer>或List<Float>。在本段出現的連接中可以找到更多信息。
5. 如何編寫一個泛型方法,讓它能接受泛型參數並返回泛型類型?
編寫泛型方法並不困難,你需要用泛型類型來替代原始類型,比如使用T, E or K,V等被廣泛認可的類型占位符。泛型方法的例子請參閱Java集合類框架。最簡單的情況下,一個泛型方法可能會像這樣:
public V put(K key, V value) { return cache.put(key, value); }
6. Java中如何使用泛型編寫帶有參數的類?
這是上一道面試題的延伸。面試官可能會要求你用泛型編寫一個類型安全的類,而不是編寫一個泛型方法。關鍵仍然是使用泛型類型來代替原始類型,而且要使用JDK中采用的標准占位符。
7. 編寫一段泛型程序來實現LRU緩存?
對於喜歡Java編程的人來說這相當於是一次練習。給你個提示,LinkedHashMap可以用來實現固定大小的LRU緩存,當LRU緩存已經滿了的時候,它會把最老的鍵值對移出緩存。LinkedHashMap提供了一個稱為removeEldestEntry()的方法,該方法會被put()和putAll()調用來刪除最老的鍵值對。
8. 你可以把List<String>傳遞給一個接受List<Object>參數的方法嗎?(見上面說明)
對任何一個不太熟悉泛型的人來說,這個Java泛型題目看起來令人疑惑,因為乍看起來String是一種Object,所以List<String>應當可以用在需要List<Object>的地方,但是事實並非如此。真這樣做的話會導致編譯錯誤。如果你再深一步考慮,你會發現Java這樣做是有意義的,因為List<Object>可以存儲任何類型的對象包括String, Integer等等,而List<String>卻只能用來存儲Strings。
9. Array中可以用泛型嗎?
這可能是Java泛型面試題中最簡單的一個了,當然前提是你要知道Array事實上並不支持泛型,這也是為什么Joshua Bloch在Effective Java一書中建議使用List來代替Array,因為List可以提供編譯期的類型安全保證,而Array卻不能。
10. 如何阻止Java中的類型未檢查的警告?
如果你把泛型和原始類型混合起來使用,例如下列代碼,Java 5的javac編譯器會產生類型未檢查的警告
,例如List<String> rawList = new ArrayList()
注意: Hello.java使用了未檢查或稱為不安全的操作;
這種警告可以使用@SuppressWarnings("unchecked")注解來屏蔽。
11、Java中List<Object>和原始類型List之間的區別?
原始類型和帶參數類型<Object>之間的主要區別是,在編譯時編譯器不會對原始類型進行類型安全檢查,卻會對帶參數的類型進行檢查,通過使用Object作為類型,可以告知編譯器該方法可以接受任何類型的對象,比如String或Integer。這道題的考察點在於對泛型中原始類型的正確理解。它們之間的第二點區別是,你可以把任何帶參數的泛型類型傳遞給接受原始類型List的方法,但卻不能把List<String>傳遞給接受List<Object>的方法,因為會產生編譯錯誤。
12、Java中List<?>和List<Object>之間的區別是什么?
這道題跟上一道題看起來很像,實質上卻完全不同。List<?> 是一個未知類型的List,而List<Object>其實是任意類型的List。你可以把List<String>, List<Integer>賦值給List<?>,卻不能把List<String>賦值給List<Object>。
13、List<String>和原始類型List之間的區別.
該題類似於“原始類型和帶參數類型之間有什么區別”。帶參數類型是類型安全的,而且其類型安全是由編譯器保證的,但原始類型List卻不是類型安全的。你不能把String之外的任何其它類型的Object存入String類型的List中,而你可以把任何類型的對象存入原始List中。使用泛型的帶參數類型你不需要進行類型轉換,但是對於原始類型,你則需要進行顯式的類型轉換。
List listOfRawTypes = new ArrayList(); listOfRawTypes.add("abc"); listOfRawTypes.add(123); //編譯器允許這樣 - 運行時卻會出現異常 String item = (String) listOfRawTypes.get(0); //需要顯式的類型轉換 item = (String) listOfRawTypes.get(1); //拋ClassCastException,因為Integer不能被轉換為String List<String> listOfString = new ArrayList(); listOfString.add("abcd"); listOfString.add(1234); //編譯錯誤,比在運行時拋異常要好 item = listOfString.get(0); //不需要顯式的類型轉換 - 編譯器自動轉換
通配符
通配符上界
3 0
因此,我們得出結論:不能往List<? extends T> 中添加任意對象,除了null。
通配符下界
無界通配符
常規使用
public static void printList(List<Object> list) { for (Object elem : list) System.out.println(elem + ""); System.out.println(); }