面試中Java泛型問題一文搞定

大黃奔跑發表於2020-12-27

微信公眾號:大黃奔跑
關注我,可瞭解更多有趣的面試相關問題。

寫在之前

本文主要向面試角度講述一個問題在面試中該如何回答,如果只想看問題答案,可以重點留意灰色背景的問題,其他的文字主要是擴充套件內容。

關於泛型,Jdk1.5以後推出來的特性,已經推出被Java程式設計師熱捧,因為其實實在在解決一些編碼問題。下面我們來看看如此重要的泛型,在面試中會怎麼考察呢。

面試問題概覽

下面我羅列一些大廠面試中,對於泛型一些常見問題。

  1. 泛型瞭解嗎?介紹一下泛型?【快手】
  2. 泛型實現的原理,為什麼要實現泛型【阿里】
  3. 泛型實現原理 【去哪兒】
  4. java的泛型,superextend的區別?泛型擦除的原理【華為】
  5. 什麼是泛型,什麼時候需要利用泛型【位元組跳動】
  6. java的泛型,有什麼缺點【騰訊】

可以看到泛型在各個大廠面試中已經成為了筆考題目,回想自己當初校招,因為泛型回答不好而錯失阿里Offer,至今仍然悔恨不已。

真實面試回顧

一個身著灰色格子襯衫,拿著閃著碩大的?logo小哥迎面走來,我心想,logo還自帶發光的,這尼瑪肯定是p7大佬了,但是剛開始我們們還是得淡定不是。

大黃同學是吧,我看你簡歷上面寫熟悉Java基礎以及常見的Java特性。那你先說說你對泛型的理解吧

此刻我們們得淡定,雖然自己準備過,也得慢慢道來。

面試官您好,泛型是JDK1.5之後提出的新概念,本意是讓同一套程式碼可以適應更多的型別。

大家都寫過議論文對吧,對於一個問題需要正反兩面說,優點、缺點,一一道來。

缺點:在沒有泛型之前一旦某個介面定義了引數為某個型別,則實現了該介面的方法必須採用同樣型別引數,不利於程式的擴充套件。
優點:

  1. 比如在建立容器的時候,指定容器持有何種型別,以便在編譯期間就能夠保證型別的正確性,而不是將錯誤留到執行的時候
  2. 無論是建立類、方法的時候都可以泛化引數,可以做到程式碼的複用。
    泛型的出現最主要目的為了更好的建立容器類

比如下面這個例子中,在定義Dog類的時候不需要指定其屬性a的含義,只需要利用T來表示,在new該物件的時候指定對應的型別即可。

/**
 * 
 * @param <T>
 */
public class Dog<T> {
    private T a;
    public Dog(T a) {
        this.a = a;
    }

    public void set(T a) {
        this.a = a;
    }
    public T get() {
        return a;
    }

    public static void main(String[] args) {
        // 在例項化的時候才確定型別
        Dog<Integer> h3 = new Dog<Integer>(new Integer(3));
        // 然後就可以不用強制型別轉化了
        Integer a = h3.get();
        // h3.set("Not an Automobile"); // Error
        // h3.set(1.11); // Error
    }
} 

面試官摸了摸筆記本,心想,回答的還可以,繼續追問。

你剛才說泛型消除了型別之間的差異,那你知道jdk底層是如何做到的嗎?

大黃:

jdk是通過型別擦除來實現泛型的,編譯器在編譯之後會擦除了所有型別相關的資訊,所以在執行時不存在任何型別相關的資訊。比如如果編寫 List<String> names = new Arraylist<>();在執行的時候,僅用List來表示。

先不要高興,面試官可能會繼續追問。

面試官:既然你說編譯時就已經擦除了,為什麼要用型別擦除呢?
大黃:這麼做是為了向前相容,為了相容jdk1.5以前的程式,確保能和Java 5之前的版本開發二進位制類庫進行相容。

好小夥,這都知道,看來之前做足了功課,還得繼續問問,試試深淺。

面試官:你知道泛型中的萬用字元吧,那你說一下什麼是限定萬用字元和非限定萬用字元。
大黃:限定萬用字元,是限定型別必須是某一個型別的子類。
比如:List<? extends Fruit> flist表示具有任何從Fruit繼承的型別的列表。
而限定萬用字元:<? super Fruit> 確保型別必須是Fruit的父類來設定型別的下界。

大黃小提醒

不要意味new一個List<? extends Fruit>flist就可以把任何的水果塞到flist裡面,很遺憾的告訴你,下面的程式編譯不通過的。

/**
 * 泛型中萬用字元的應用
 */
public class GenericsAndCovariance {
    public static void main(String[] args) {
        // new一個水果籃子
        List<? extends Fruit> flist = new ArrayList<Apple>();
        // 下面編譯不會通過
        flist.add(new Apple());
        // 下面編譯不會通過
        flist.add(new Orange());
        // 下面編譯同樣不會通過
        // flist.add(new Fruit());
    }
}

看到這個程式是否感覺到有點懵,我自己建立的水果籃子,我無法往裡面放蘋果、橙子,甚至連籃子物件也不能放。那我這個籃子還有屁用哦。
是的,確實沒有什麼用,似乎和預期不太一樣。
但是想想也合乎道理。如果不知道list持有什麼物件,那麼怎麼樣才能安全地向其中新增物件呢?如果允許了,則獲取元素的時候,就會出錯。

下面面試繼續:

面試官:那你再說說泛型可以用到什麼地方?
大黃:泛型即可用用於類定義中、介面的定義、方法的定義、同時也廣泛用於容器中。

  1. 在類中使用泛型,可以讓類中的屬性由泛型定義,而不需要提前知道屬性的型別。
  2. 在介面中使用泛型,可以讓介面方法的返回值按照各自實現自由定義。這也是常見的工廠設計模式的一種應用。
  3. 在方法中利用泛型,可以做到方法的複用,同一個方法可以傳入不同型別引數。實現時只需要將泛型引數列表置於返回值之前即可,JDK1.8中Stream中很多流式方法的定義採用了泛型。

記得加上這句話

我覺得,使用泛型機制最吸引人的地方,在使用容器的地方,比如List、Set、Map,因為在1.5以前,在泛型出現之前,當將一個物件放到容器中,這個物件會預設的轉化為Object型別,因此會丟失掉型別資訊,可能將一個Apple物件放到Orange容器中,然後試圖從Orange容器中獲取物件時,得到的是一個Apple,強制轉化必然出問題,會丟擲RuntimeException,但是有了泛型之後,這種問題在寫程式碼,也就是說在編譯期間就會暴露,而不是在執行時暴露。

比如下面定義介面用於生成不同的物件:

/**
 * 定義一個生成器
 * @param <T>
 */
public interface Generator<T> {
    /**
     * 定義生成下一個物件
     * @return
     */
    T next();
}

/**
 * 利用泛型生成器來生成費波列切數
 */
public class Fibonacci implements Generator<Integer> {
    private int count = 0;

    public static void main(String[] args) {
        Fibonacci gen = new Fibonacci();
        for (int i = 0; i < 18; i++)
            System.out.print(gen.next() + " ");
    }
    /**
     * 實現介面的方法,返回值為Integer
     * @return
     */
    public Integer next() {
        return fib(count++);
    }
    /**
     * 定義費波列切數
     * @param n
     * @return
     */
    private int fib(int n) {
        if (n < 2) return 1;
        return fib(n - 2) + fib(n - 1);
    }
}

泛型方法的應用:

public class GenericMethods {

    public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        gm.f("");
        gm.f(1);
        gm.f(gm);
    }

    /**
     * 定義泛型方法
     * @param x         方法的引數為泛型
     * @param <T>
     */
    public <T> void f(T x) {
        System.out.println(x.getClass().getName());
    }
}

回答到這裡,關於泛型的問題基本上總結的差不多了。

總結

本身主要圍繞開頭的幾個真正的面試題展開,簡單來說,泛型是什麼?為什麼要有泛型?泛型如何實現的?泛型有哪些用處。
個人觀點,一個技術從剛開始學習的時候,從四方面思考,能夠事半功倍。

最後大黃分享多年面試心得。面試中,面對一個問題,大概按照總分的邏輯回答即可。先直接丟擲結論,然後舉例論證自己的結論。一定要第一時間抓住面試官的心裡,否則容易給人抓不著重點或者不著邊際的印象。

番外

另外,關注大黃奔跑公眾號,第一時間收穫獨家整理的面試實戰記錄及面試知識點總結。

我是大黃,一個只會寫HelloWorld的程式設計師,我們們下期見。

關注大黃,充當offer收割機

相關文章