面試官問我:“泛型擦除是什麼,會帶來什麼問題?”


面試官問我:“泛型擦除是什麼,會帶來什麼問題?”

前言

這是我之前在抖音二面的時候自我感覺沒有答好的一題。因為我的中心只是圍繞在了T被Object替換的問題上了,並沒有去講解他會帶來的問題。

思維導圖

面試官問我:“泛型擦除是什麼,會帶來什麼問題?”

什麼是泛型擦除?

其實我們很常見這個問題,你甚至經常用,只是沒有去注意罷了,但是很不碰巧這樣的問題就容易被面試官抓住。下面先來看一段代碼吧。

<code>List list = new ArrayList();
List listString = new ArrayList<string>();
List listInteger = new ArrayList<integer>();/<integer>/<string>/<code>

這幾段代碼簡單、粗暴、又帶有很濃厚的熟悉感是吧。那我接下來要把一個數字1插入到這三段不一樣的代碼中了。

作為讀者的你可能現在已經黑人問號了????你肯定有很多疑問,這明顯不一樣啊,怎麼可能。

<code>public class Main {
public static void main(String[] args) {
List list = new ArrayList();
List listString = new ArrayList<string>();
List listInteger = new ArrayList<integer>();

try {
list.getClass().getMethod("add", Object.class).invoke(list, 1);
listString.getClass().getMethod("add", Object.class).invoke(listString, 1);
// 給不服氣的讀者們的測試之處,你可以改成字符串來嘗試。
listInteger.getClass().getMethod("add", Object.class).invoke(listInteger, 1);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("list size:" + list.size());
System.out.println("listString size:" + listString.size());
System.out.println("listInteger size:" + listInteger.size());
}

}
/<integer>/<string>/<code>
面試官問我:“泛型擦除是什麼,會帶來什麼問題?”

不好意思,有圖有真相,我就是插進去了,要是你還不信,我還真沒辦法了。

探索真相

上述的就是泛型擦除的一種表現了,但是為了更好的理解,當然要更深入了是吧。雖然List很大,但卻也不是不能看看。

兩個關鍵點,來驗證一下:

  1. 數據存儲類型
  2. 數據獲取
<code>// 先來看看畫了一個大餅的List
// 能夠過很清楚的看到泛型E
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable{
// 第一個關鍵點
// 還沒開始就出問題的存儲類型
// 難道不應該也是一個泛型E?
transient Object[] elementData;

public E get(int index) {
rangeCheck(index);

return elementData(index); // 1---->
}

// 由1直接調用的函數
// 第二個關鍵點,強制轉化得來的數據
E elementData(int index) {
return (E) elementData[index];
}
}
/<code>

我想,其實你也能夠懂了,這個所謂的泛型T最後會被轉化為一個Object,最後又通過強制轉化來進行一個轉變。從這裡我們也就能夠知道為什麼我們的數據從前面過來的時候,String類型數據能夠直接被Integer進行接收了。

帶來什麼樣的問題?

(1) 強制類型轉化

這個問題的結果我們已經在上述文章中提及到了,通過反射的方式去進行插入的時候,我們的數據就會發生錯誤。

如果我們在一個List<integer>中在不知情的情況下插入了一個String類型的數值,那這種重大錯誤,我們該找誰去說呢。/<integer>

(2)引用傳遞問題

上面的問題中,我們已經說過了T將在後期被轉義成Object,那我們對引用也進行一個轉化,是否行得通呢?

<code>List<string> listObject = new ArrayList<object>();
List<object> listObject = new ArrayList<string>();/<string>/<object>/<object>/<string>/<code>

如果你這樣寫,在我們的檢查階段,會報錯。但是從邏輯意義上來說,其實你真的有錯嗎?

假設說我們的第一種方案是正確的,那麼其實就是將一堆Object數據存入,然後再由上面所說的強制轉化一般,轉化成String類型,聽起來完全ok,因為在List中本來存儲數據的方式就是Object。但其實是會出現ClassCastException的問題,因為Object是萬物的基類,但是強轉是為子類向父類準備的措施。

再來假設說我們的第二種方案是正確的,這個時候,根據上方的數據String存入,但是有什麼意義存在呢?最後都還是要成Object的,你還不如就直接是Object。

解決方案

其實很簡單,如果看過一些公開課想來就見過這樣的用法。

<code>public class Part {

private T val;

public T getVal() {
return val;
}

public void setVal(T val) {
this.val = val;
}
}
/<code>

相比較於之前的Part而言,他多了的語句,其實這就是將基類重新規劃的操作,就算被編譯,虛擬機也會知道將數據轉化為Parent而不是直接用Object來直接進行替代。

應用場景

該部分的思路來自於Java泛型中extends和super的區別?

上面我們說過了解決方案,使用

。其實這只是一種方案,在不同的場景下,我們需要加入不同的使用方法。另外官方也是提倡使用這樣的方法的,但是我們為了避免我們上述的錯誤,自然需要給出一些使用場景了。

基於的其實是兩種場景,一個是擴展型super,一個是繼承型extends。下面都用一個列表來舉例子。

統一繼承順序

<code>// 承載者
class Plate{
private T item;
public Plate(T t){item=t;}
public void set(T t){item=t;}
public T get(){return item;}
}

// Lev 1
class Food{}

// Lev 2
class Fruit extends Food{}
class Meat extends Food{}

//Lev 3
class Apple extends Fruit{}
class Banana extends Fruit{}
class Pork extends Meat{}
class Beef extends Meat{}

//Lev 4
class RedApple extends Apple{}
class GreenApple extends Apple{}
/<code>

繼承型的用處是什麼呢?

其實他期待的就是這整個列表的數據的基礎都是來自我們的Parent,這樣獲取的數據全部人的父類其實都是來自於我們的Parent了,你可以叫這個列表為Parent家族。所以也可以說這是一個適合頻繁讀取的方案。

面試官問我:“泛型擦除是什麼,會帶來什麼問題?”

面試官問我:“泛型擦除是什麼,會帶來什麼問題?”

<code>Plate extends Fruit> p1=new Plate<apple>(new Apple());
Plate extends Fruit> p2=new Plate<apple>(new Beef()); // 檢查不通過

// 修改數據不通過
p1.set(new Banana());

// 數據獲取一切正常
// 但是他只能精確到由我們定義的Fruit
Fruit result = p1.get();/<apple>/<apple>/<code>

擴展型的作用是什麼呢?

你可以把它當成一種兼容工具,由super修飾,說明兼容這個類,通過這樣的方式比較適用於去存放上面所說的Parent列表中的數據。這是一個適合頻繁插入的方案。

面試官問我:“泛型擦除是什麼,會帶來什麼問題?”

面試官問我:“泛型擦除是什麼,會帶來什麼問題?”

<code>// 填寫Food的位置,級別一定要大於或等於Fruit
Plate super Fruit> p1=new Plate<food>(new Apple());
// 和extends 不同可以進行存儲
p1.set(new Banana());
// get方法
Banana result1 = p1.get(); // 會報錯,一定要經過強制轉化,因為返回的只是一個Object
Object result2 = p1.get(); // 返回一個Object數據我們已經屬於快要丟失掉全部數據了,所以不適合讀取/<food>/<code>

以上就是我的學習成果,如果有什麼我沒有思考到的地方或是文章內存在錯誤,歡迎與我分享。

最後

最後我想說:對於程序員來說,要學習的知識內容、技術有太多太多,要想不被環境淘汰就只有不斷提升自己,從來都是我們去適應環境,而不是環境來適應我們!

面試官問我:“泛型擦除是什麼,會帶來什麼問題?”

在這裡小編分享一份自己收錄整理上述技術體系圖相關的幾十套騰訊、頭條、阿里、美團等公司19年的面試題

,把技術點整理成了視頻和PDF(實際上比預期多花了不少精力),包含知識脈絡 + 諸多細節,由於篇幅有限,這裡以圖片的形式給大家展示一部分。

還有 高級架構技術進階腦圖、Android開發面試專題資料,高級進階架構資料 幫助大家學習提升進階,也節省大家在網上搜索資料的時間來學習,也可以分享給身邊好友一起學習。

相信它會給大家帶來很多收穫:

面試官問我:“泛型擦除是什麼,會帶來什麼問題?”

【Android進階學習視頻】、【全套Android面試秘籍PDF】、【Android開發核心知識點筆記】可以 私信我【安卓】免費獲取!

當程序員容易,當一個優秀的程序員是需要不斷學習的,從初級程序員到高級程序員,從初級架構師到資深架構師,或者走向管理,從技術經理到技術總監,每個階段都需要掌握不同的能力。早早確定自己的職業方向,才能在工作和能力提升中甩開同齡人。


分享到:


相關文章: