抖音二面栽在泛型擦除?答應我,別和我倒在同一地方!
前言
這是我在抖音二面的時候自我感覺沒有答好的一題。因為我的中心只是圍繞在了
T
被
Object
替換的問題上了,並沒有去講解他會帶來的問題。
思維導圖
什麼是泛型擦除?
其實我們很常見這個問題,你甚至經常用,只是沒有去注意罷了,但是很不碰巧這樣的問題就容易被面試官抓住。下面先來看一段程式碼吧。
List list = new ArrayList(); List listString = new ArrayList<String>(); List listInteger = new ArrayList<Integer>();
這幾段程式碼簡單、粗暴、又帶有很濃厚的熟悉感是吧。那我接下來要把一個
數字1
插入到這三段不一樣的程式碼中了。
作為讀者的你可能現在已經黑人問號了????你肯定有很多疑問,這明顯不一樣啊,怎麼可能。
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()); } }
不好意思,有圖有真相,我就是插進去了,要是你還不信,我還真沒辦法了。
探索真相
上述的就是泛型擦除的一種表現了,但是為了更好的理解,當然要更深入了是吧。雖然
List
很大,但卻也不是不能看看。
兩個關鍵點,來驗證一下:
- 資料儲存型別
- 資料獲取
// 先來看看畫了一個大餅的List // 能夠過很清楚的看到泛型E public class ArrayList<E> extends AbstractList<E> implements List<E>, 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]; } }
我想,其實你也能夠懂了,這個所謂的泛型
T
最後會被轉化為一個
Object
,最後又透過強制轉化來進行一個轉變。從這裡我們也就能夠知道為什麼我們的資料從前面過來的時候,
String
型別資料能夠直接被
Integer
進行接收了。
帶來什麼樣的問題?
(1) 強制型別轉化
這個問題的結果我們已經在上述文章中提及到了,透過反射的方式去進行插入的時候,我們的資料就會發生錯誤。
如果我們在一個
List<Integer>
中在不知情的情況下插入了一個
String
型別的數值,那這種重大錯誤,我們該找誰去說呢。
(2)引用傳遞問題
上面的問題中,我們已經說過了
T
將在後期被轉義成
Object
,那我們對引用也進行一個轉化,是否行得通呢?
List<String> listObject = new ArrayList<Object>(); List<Object> listObject = new ArrayList<String>();
如果你這樣寫,在我們的檢查階段,會報錯。但是從邏輯意義上來說,其實你真的有錯嗎?
假設說我們的第一種方案是正確的,那麼其實就是將一堆
Object
資料存入,然後再由上面所說的強制轉化一般,轉化成
String
型別,聽起來完全ok,因為在
List
中本來儲存資料的方式就是
Object
。但其實是會出現
ClassCastException
的問題,因為
Object
是萬物的基類,但是強轉是為子類向父類準備的措施。
再來假設說我們的第二種方案是正確的,這個時候,根據上方的資料
String
存入,但是有什麼意義存在呢?最後都還是要成
Object
的,你還不如就直接是
Object
。
解決方案
其實很簡單,如果看過一些公開課想來就見過這樣的用法。
public class Part<T extends Parent> { private T val; public T getVal() { return val; } public void setVal(T val) { this.val = val; } }
相比較於之前的
Part
而言,他多了
<T extends Parent>
的語句,其實這就是將基類重新規劃的操作,就算被編譯,虛擬機器也會知道將資料轉化為
Parent
而不是直接用
Object
來直接進行替代。
應用場景
這裡需要感謝給我提出問題的大佬讀者:挖掘機技術
該部分的思路來自於 Java泛型中extends和super的區別?
上面我們說過了解決方案,使用
<T extends Parent>
。其實這只是一種方案,在不同的場景下,我們需要加入不同的使用方法。另外官方也是提倡使用這樣的方法的,但是我們為了避免我們上述的錯誤,自然需要給出一些使用場景了。
基於的其實是兩種場景,一個是擴充套件型
super
,一個是繼承型
extends
。下面都用一個列表來舉例子。
統一繼承順序
// 承載者 class Plate<T>{ 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{}
<T extends Parent>
繼承型的用處是什麼呢?
其實他期待的就是這整個列表的資料的基礎都是來自我們的
Parent
,這樣獲取的資料全部人的父類其實都是來自於我們的
Parent
了,你可以叫這個列表為
Parent
家族。所以也可以說這是一個適合頻繁讀取的方案。
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();
<T super Parent>
擴充套件型的作用是什麼呢?
你可以把它當成一種相容工具,由
super
修飾,說明相容這個類,透過這樣的方式比較適用於去存放上面所說的
Parent
列表中的資料。這是一個適合頻繁插入的方案。
// 填寫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資料我們已經屬於快要丟失掉全部資料了,所以不適合讀取
以上就是我的學習成果,如果有什麼我沒有思考到的地方或是文章記憶體在錯誤,歡迎與我分享。
最後有話說
附上我的Android核心技術學習大綱,獲取相關內容來我的GitHub一起玩耍:
vx:xx1341452
對於進階這條路而言,學習是會有回報的!
你把你的時間投資在學習上,就意味著你可以收穫技能,更有機會增加收入。
在這裡分享我的Android學習PDF大全來學習,這份Android學習PDF大全真的包含了方方面面了,內含Java基礎知識點、Android基礎、Android進階延伸、演算法合集等等
我的這份學習合集,可以有效的幫助大家掌握知識點。
總之也是在這裡幫助大家學習提升進階,也節省大家在網上搜尋資料的時間來學習,也可以分享給身邊好友一起學習
獲取方式:關注我看個人介紹,或直接 點選我免費領取
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2679290/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Java泛型與型別擦除Java泛型型別
- Java泛型型別擦除問題Java泛型型別
- Java 泛型,你瞭解型別擦除嗎?Java泛型型別
- 泛型擦除的原理泛型
- 面試官:說說什麼是泛型的型別擦除?面試泛型型別
- Swift 型別擦除Swift型別
- Java泛型(三):型別擦除帶來的約束與侷限性Java泛型型別
- 一句話,講清楚java泛型的本質(非型別擦除)Java泛型型別
- 初探Java型別擦除Java型別
- Java™ 教程(型別擦除)Java型別
- 泛型的型別擦除後,fastjson反序列化時如何還原?泛型型別ASTJSON
- [譯]Swift 中的型別擦除Swift型別
- Java 泛型中易混淆的地方Java泛型
- 泛型(二)泛型
- 泛型類、泛型方法及泛型應用泛型
- 型別 VS 泛型型別泛型
- TypeScript 泛型型別TypeScript泛型型別
- 從 Swift 中的序列到型別擦除Swift型別
- 泛型型別(.NET 指南)泛型型別
- 我理解的 Java 泛型Java泛型
- 泛型類、泛型方法、型別萬用字元的使用泛型型別字元
- 答應我,別在go專案中用init()了Go
- Java™ 教程(泛型原始型別)Java泛型型別
- 型別與泛型標記型別泛型
- Scala 泛型型別和方法泛型型別
- 泛型及其應用泛型
- 基本型別、字串該注意的地方型別字串
- 我和我的DBA之路
- 詳解C#泛型(二)C#泛型
- TypeScript 基本型別和泛型的使用TypeScript型別泛型
- C# 泛型 引用型別約束 值型別約束C#泛型型別
- Java基礎系列(三十六):泛型中需要注意的地方Java泛型
- 我和我的智慧聯接
- Java泛型知識點:泛型類、泛型介面和泛型方法Java泛型
- 【原創】【自制系列】自制stack型別(泛型)型別泛型
- java中泛型之型別萬用字元(?)Java泛型型別字元
- 泛型作為返回型別的寫法泛型型別
- Java中建立泛型型別的例項Java泛型型別