Java 泛型三連炮,你能接得住嗎?

Photo By Instagram sooyaaa

上期問題

利用 Java 中的動態綁定我們可以實現很多有意思的玩法。例如

Object obj = new String();

集合類想必是小夥伴們低頭不見擡頭見的類了,那麼你有沒有想過集合類是否可以這樣玩?

List<Object> list = new ArrayList<Integer>();

不瞞你說,這是毛毛蟲在一次面試中遇到的真實的面試題,當時被問的一臉懵。 如果是你,你知道怎麼回答嗎?

我的答案

首先我要告訴你的是,Java 集合類不允許這麼玩,這樣編譯器會直接報錯編譯不通過。

我們都知道賦值一定需要是 is a 的關係,雖然 Object 和 Integer 是父子關係,但是很可惜 List<Object> 和 new ArrayList<Integer> 不是父子關係。當你這麼回答時候,面試官臉上露出了狡黠的笑容,假設可以這麼玩,那會有什麼樣的問題呢?

好吧,假設 Java 允許我們這樣寫

List<Object> objList = new ArrayList<Integer>();

那麼接下來編譯器需要來確定一下容器的具體類型,因爲容器裏面必須存放一種確定的對象類型,不然泛型也就沒有誕生的意義了。那究竟是 Object 還是 Integer 呢?

假設一

假設它最終確定下來存放的是 Object,那就和如下代碼是一樣的效果了

List<Object> list = new ArrayList<>();

這種寫法是 Java 7 引入的寫法,官方稱之爲 diamond ,就是那一對空着的尖括號,使用這種寫法時候編譯器會自動推算出類型爲 Object。這樣就相當於賦值語句 ArrayList 中的泛型 Integer 毫無意義,那麼無意義的操作 Java 顯然是不允許的。

假設二

我們再假設最終確定下來的是存放的是 Integer,這樣問題就更大了。因爲我們都知道 Object 是所有類的基類,這就代表着 objList 可以添加任何類型的對象。即我們可以做如下操作

objList.add(new String());
objList.add(new Integer());

然後我們使用容器裏面的對象的時候取出來需要將它強制轉換爲 Integer 對象,如果容器中本來就存放的是 Integer 的對象還好,如若不是就會出現 ClassCastException。有沒有發現,這樣不是恰如回退到沒有泛型的 Java 版本了,即 Java 1.5 之前。我們使用容器不能在編譯期間保證它的類型安全了,歷史回退這種傻傻的操作Java 也絕對是不允許的。

經過如上的一通分析我們發現泛型它就是不能這麼玩,而且這麼玩也是毫無意義。

這個時候面試官微微的點點頭,表示略有贊同,你以爲終於可以結束這樣各種假設的騷操作了。面試官嘴角再次漏出狡黠的笑容,前面你說 List<Object> 和 ArrayList<Integer> 不是父子關係,那泛型裏面有父子關係嗎或者說 List<Object> 和 ArrayList<Integer> 是否存在着某種關係?

好吧。它們是兄弟,都是 List<?> 的子類。也就是說,你可以這麼玩:

List<?> list1 = new ArrayList<Object>();
List<?> list2 = new ArrayList<Integer>();

如上是 2 段代碼是合法的,容器造出來了。那讓我們來給容器裏面塞一些對象進去吧。

list1.add(new Object());

不好意思你不能這麼寫,因爲這 2 個容器裏面可以存儲任何東西,導致編譯器都無法判斷到底給裏面會存儲什麼,它倆是 Read Only 哦。What? 那這有啥用呢?

好吧,假如有這樣一個需求:寫一個方法可以累加數字容器裏面的元素值,那我們可以這樣去寫

public static long sumNumbers(List<?> list) {
    Long sum = 0l;
    for (Object num : list) {
        sum += ((Number) num).longValue();
    }
    return sum;
}

你可能注意到了,沒錯這裏經歷了一次強制類型轉換,萬一方法的調用方傳入的是 new ArrayList<String>() 那麼此處你可能會接到一個 ClassCastException 了。那有沒有好的解決辦法呢?有!

public static long sumNumbers(List<? extends Number> list) {/****/}

我們將方法的參數改爲了 List<? extends Number> list ,這樣當調用方試圖傳入非 Number 類型的容器實例時候在編譯期就會直接報錯咯,嘿嘿。

當然啦,你也可以通過 super 關鍵字來設置泛型參數的下限,例如 List<? super Number> list  ,那這個時候調用方法的時候就只能傳入泛型參數是 Number 或者 Number 的父親級別的容器了。

好了,這就是本題的答案啦。想必回答到此處以後,面試官心裏一定心裏在想:“小夥子有點東西啊”。

以上即爲昨天的問題的答案,小夥伴們對這個答案是否滿意呢?歡迎留言和我討論。

又要到年末了,你是不是又悄咪咪的開始看機會啦。爲了廣大小夥伴能充足電量,能順利通過 BAT 的面試官無情三連炮,我特意推出大型刷題節目。每天一道題目,第二天給答案,前一天給小夥伴們獨立思考的機會。

點下“在看”,鼓勵一下?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章