java泛型常見面試題


背景:泛型這個知識點平時用的不多,但是在面試的時候很容就被問到,所以還是要准備一些基礎的知識儲備。

面試舊敵之 Java 泛型 :主要概念及特點

“泛型” 意味着編寫的代碼可以被不同類型的對象所重用。

泛型是在JDK1.5之后出現的。

泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。

可以看到,使用 Object 來實現通用、不同類型的處理,有這么兩個缺點:

  1. 每次使用時都需要強制轉換成想要的類型
  2. 在編譯時編譯器並不知道類型轉換是否正常,運行時才知道,不安全

根據《Java 編程思想》中的描述,泛型出現的動機在於:

有許多原因促成了泛型的出現,而最引人注意的一個原因,就是為了創建容器類

實際上引入泛型的主要目標有以下幾點:

    • 類型安全 
      • 泛型的主要目標是提高 Java 程序的類型安全
      • 編譯時期就可以檢查出因 Java 類型不正確導致的 ClassCastException 異常
      • 符合越早出錯代價越小原則
    • 消除強制類型轉換 
      • 泛型的一個附帶好處是,使用時直接得到目標類型,消除許多強制類型轉換
      • 所得即所需,這使得代碼更加可讀,並且減少了出錯機會
    • 潛在的性能收益 
      • 由於泛型的實現方式,支持泛型(幾乎)不需要 JVM 或類文件更改
      • 所有工作都在編譯器中完成
      • 編譯器生成的代碼跟不使用泛型(和強制類型轉換)時所寫的代碼幾乎一致,只是更能確保類型安全而已

泛型類 

泛型接口

泛型方法

泛型的通配符

無限制通配符

extends 關鍵字聲明了類型的上界,表示參數化的類型可能是所指定的類型,或者是此類型的子類

super 關鍵字聲明了類型的下界,表示參數化的類型可能是指定的類型,或者是此類型的父類

通配符比較

通過上面的例子我們可以知道,無限制通配符 < ?> 和 Object 有些相似,用於表示無限制或者不確定范圍的場景。

兩種有限制通配形式 < ? super E> 和 < ? extends E> 也比較容易混淆,我們再來比較下。

你了解泛型通配符與上下界嗎?

ps:上下限用圖說明

它們的目的都是為了使方法接口更為靈活,可以接受更為廣泛的類型。

  • < ? super E> 用於靈活寫入或比較,使得對象可以寫入父類型的容器,使得父類型的比較方法可以應用於子類對象。
  • < ? extends E> 用於靈活讀取,使得方法可以讀取 E 或 E 的任意子類型的容器對象。

用《Effective Java》 中的一個短語來加深理解:

為了獲得最大限度的靈活性,要在表示 生產者或者消費者 的輸入參數上使用通配符,使用的規則就是:生產者有上限、消費者有下限:

PECS: producer-extends, costumer-super

因此使用通配符的基本原則:

  • 如果參數化類型表示一個 T 的生產者,使用 < ? extends T>;
  • 如果它表示一個 T 的消費者,就使用 < ? super T>;
  • 如果既是生產又是消費,那使用通配符就沒什么意義了,因為你需要的是精確的參數類型。

小總結一下:

  • T 的生產者的意思就是結果會返回 T,這就要求返回一個具體的類型,必須有上限才夠具體;
  • T 的消費者的意思是要操作 T,這就要求操作的容器要夠大,所以容器需要是 T 的父類,即 super T;

泛型的類型擦除

Java 中的泛型和 C++ 中的模板有一個很大的不同:

  • C++ 中模板的實例化會為每一種類型都產生一套不同的代碼,這就是所謂的代碼膨脹。
  • Java 中並不會產生這個問題。虛擬機中並沒有泛型類型對象,所有的對象都是普通類。

(摘自:blog.csdn.net/fw0124/arti…

在 Java 中,泛型是 Java 編譯器的概念,用泛型編寫的 Java 程序和普通的 Java 程序基本相同,只是多了一些參數化的類型同時少了一些類型轉換。

實際上泛型程序也是首先被轉化成一般的、不帶泛型的 Java 程序后再進行處理的,編譯器自動完成了從 Generic Java 到普通 Java 的翻譯,Java 虛擬機運行時對泛型基本一無所知。

當編譯器對帶有泛型的java代碼進行編譯時,它會去執行類型檢查和類型推斷,然后生成普通的不帶泛型的字節碼,這種普通的字節碼可以被一般的 Java 虛擬機接收並執行,這在就叫做 類型擦除(type erasure)

泛型的規則

  • 泛型的參數類型只能是類(包括自定義類),不能是簡單類型。
  • 同一種泛型可以對應多個版本(因為參數類型是不確定的),不同版本的泛型類實例是不兼容的。
  • 泛型的類型參數可以有多個
  • 泛型的參數類型可以使用 extends 語句,習慣上稱為“有界類型”
  • 泛型的參數類型還可以是通配符類型,例如 Class

泛型的使用場景

當類中要操作的引用數據類型不確定的時候,過去使用 Object 來完成擴展,JDK 1.5后推薦使用泛型來完成擴展,同時保證安全性。

總結

1.上面說到使用 Object 來達到復用,會失去泛型在安全性和直觀表達性上的優勢,那為什么 ArrayList 等源碼中的還能看到使用 Object 作為類型?

根據《Effective Java》中所述,這里涉及到一個 “移植兼容性”

泛型出現時,Java 平台即將進入它的第二個十年,在此之前已經存在了大量沒有使用泛型的 Java 代碼。人們認為讓這些代碼全部保持合法,並且能夠與使用泛型的新代碼互用,非常重要。

這樣都是為了兼容,新代碼里要使用泛型而不是原始類型。

2.泛型是通過擦除來實現的。因此泛型只在編譯時強化它的類型信息,而在運行時丟棄(或者擦除)它的元素類型信息。擦除使得使用泛型的代碼可以和沒有使用泛型的代碼隨意互用。

3.如果類型參數在方法聲明中只出現一次,可以用通配符代替它。

比如下面的 swap 方法,用於交換指定 List 中的兩個位置的元素:

private  void swap(List list, int i, int j) {
    //...
}

只出現了一次 類型參數,沒有必要聲明,完全可以用通配符代替:

private void swap(List list, int i, int j){
    //...
}

對比一下,第二種更加簡單清晰吧。

4.數組中不能使用泛型

這可能是 Java 泛型面試題中最簡單的一個了,當然前提是你要知道 Array 事實上並不支持泛型,這也是為什么 Joshua Bloch 在 《Effective Java》一書中建議使用 List 來代替 Array,因為 List 可以提供編譯期的類型安全保證,而 Array 卻不能。

5.Java 中 List 和原始類型 List 之間的區別?

原始類型和帶參數類型 之間的主要區別是:

  • 在編譯時編譯器不會對原始類型進行類型安全檢查,卻會對帶參數的類型進行檢查
  • 通過使用 Object 作為類型,可以告知編譯器該方法可以接受任何類型的對象,比如String 或 Integer
  • 你可以把任何帶參數的類型傳遞給原始類型 List,但卻不能把 List< String> 傳遞給接受 List< Object> 的方法,因為泛型的不可變性,會產生編譯錯誤。

這道題的考察點在於對泛型中原始類型的正確理解。

ps:這個地方還沒理解。。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM