提給程式設計師的10道Java泛型面試題

oschina發表於2013-10-19

  關於泛型的面試題在 Java面試中變得越來越常見,因為 Java 5問世已經有相當長的時間了,越來越多的應用已經遷移到Java 5上來了,並且幾乎所有新的Java開發工作也都是在Tiger(Java 5的專案代號)版本上進行的。泛型和其它Java 5特性比如 列舉自動拆裝箱可變引數,還有集合工具類比如 CountDownLatchCyclicBarrier BlockingQueue ,這些在Java面試中變得越來越流行。如果你對 泛型的限定萬用字元和非限定萬用字元不熟悉,那麼泛型面試題就會變得非常的棘手。 Java內部 泛型是怎麼工作的呢,答案是型別擦除,此外程式設計師也應當精通於編寫帶引數的Java泛型類和泛型方法。準備泛型面試的最好的方法是,嘗試編寫一些簡單的程式來了解泛型的各種特性。而本文中,我們將會看到一些流行的Java泛型面試題及其答案。順便說一下,Javarevisited網站上提供了很多資料來幫助你更好的為Java和J2EE面試做準備,你可以檢視 15個執行緒面試題排名前10的Java集合類面試題來準備多執行緒和集合類方面的知識,另外還有關於Spring、Struts、JSP和Servlet的問答文章。如果你是GUI開發人員,並且使用的是Java Swing技術,那麼你也可以檢視投資銀行面試中通常會問到的 Java Swing面試題

 Java泛型面試題

  1. Java中的泛型是什麼 ? 使用泛型的好處是什麼?

  這是在各種Java泛型面試中,一開場你就會被問到的問題中的一個,主要集中在初級和中級面試中。那些擁有Java1.4或更早版本的開發背景的人都知道,在集合中儲存物件並在使用前進行型別轉換是多麼的不方便。泛型防止了那種情況的發生。它提供了編譯期的型別安全,確保你只能把正確型別的物件放入集合中,避免了在執行時出現ClassCastException。

  2. Java的泛型是如何工作的 ? 什麼是型別擦除 ?

  這是一道更好的泛型面試題。泛型是通過型別擦除來實現的,編譯器在編譯時擦除了所有型別相關的資訊,所以在執行時不存在任何型別相關的資訊。例如List<String>在執行時僅用一個List來表示。這樣做的目的,是確保能和Java 5之前的版本開發二進位制類庫進行相容。你無法在執行時訪問到型別引數,因為編譯器已經把泛型型別轉換成了原始型別。根據你對這個泛型問題的回答情況,你會得到一些後續提問,比如為什麼泛型是由型別擦除來實現的或者給你展示一些會導致編譯器出錯的錯誤泛型程式碼。請閱讀我的Java中泛型是如何工作的來了解更多資訊。

  3. 什麼是泛型中的限定萬用字元和非限定萬用字元 ?

  這是另一個非常流行的Java泛型面試題。限定萬用字元對型別進行了限制。有兩種限定萬用字元,一種是<? 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()呼叫來刪除最老的鍵值對。當然,如果你已經編寫了一個可執行的JUnit測試,你也可以隨意編寫你自己的實現程式碼。

  8. 你可以把List<String>傳遞給一個接受List<Object>引數的方法嗎?

  對任何一個不太熟悉泛型的人來說,這個Java泛型題目看起來令人疑惑,因為乍看起來String是一種Object,所以List<String>應當可以用在需要List<Object>的地方,但是事實並非如此。真這樣做的話會導致編譯錯誤。如果你再深一步考慮,你會發現Java這樣做是有意義的,因為List<Object>可以儲存任何型別的物件包括String, Integer等等,而List<String>卻只能用來儲存Strings。

List<Object> objectList;
List<String> stringList;
     
objectList = stringList;  //compilation error incompatible types

  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")註解來遮蔽。

  Java泛型面試題補充更新:

  我手頭又拿到了幾個Java泛型面試題跟大家分享下,這幾道題集中在泛型型別和原始型別的區別上,以及我們是否可以用Object來代替限定萬用字元的使用等等:

  Java中List<Object>和原始型別List之間的區別?

  原始型別和帶引數型別<Object>之間的主要區別是,在編譯時編譯器不會對原始型別進行型別安全檢查,卻會對帶引數的型別進行檢查,通過使用Object作為型別,可以告知編譯器該方法可以接受任何型別的物件,比如String或Integer。這道題的考察點在於對泛型中原始型別的正確理解。它們之間的第二點區別是,你可以把任何帶引數的型別傳遞給原始型別List,但卻不能把List<String>傳遞給接受List<Object>的方法,因為會產生變異錯誤。更多詳細資訊請參閱Java中的泛型是如何工作的

  Java中List<?>和List<Object>之間的區別是什麼?

  這道題跟上一道題看起來很像,實質上卻完全不同。List<?> 是一個未知型別的List,而List<Object>其實是任意型別的List。你可以把List<String>, List<Integer>賦值給List<?>,卻不能把List<String>賦值給List<Object>。     

List<?> listOfAnyType;
List<Object> listOfObject = new ArrayList<Object>();
List<String> listOfString = new ArrayList<String>();
List<Integer> listOfInteger = new ArrayList<Integer>();
     
listOfAnyType = listOfString; //legal
listOfAnyType = listOfInteger; //legal
listOfObjectType = (List<Object>) listOfString; //compiler error - in-convertible types 

  想了解更多關於萬用字元的資訊請檢視Java中的泛型萬用字元示例

  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); //不需要顯式的型別轉換 - 編譯器自動轉換 

  這些都是Java泛型面試中 頻繁出現的問題及其答案。所有這些面試題都不困難,其實它們都是基於泛型的基礎知識。任何對泛型有不錯了解的Java程式設計師都肯定熟知這些泛型題目。如果你有任何好的面試題,不管是在什麼面試中碰到的,或者如果你想知道任何Java泛型面試題的答案,請在評論中留言。

  原文地址:10-interview-questions-on-java-generics

相關文章