聽了他講的泛型,我就明白為什麼他的工資比我多30萬了!
- 2019 年 12 月 19 日
- 筆記
閱讀文本大概需要 6 分鐘。
1
類 型 擦 除
Java是怎麼實現泛型的?不錯,類型擦除。Java編譯器將源碼編譯成位元組碼的時候會將你在源碼中聲明的類型進行擦除,比如:
List<String> list = new ArrayList<String>();
在編譯後,位元組碼中只有List,運行在JVM中也是一樣的,那你可能會有疑問,既然將類型擦除了,那為什麼我聲明的泛型為String類型時,不能往裡add一個整型的數據呢?這是因為編譯器在編譯前會進行類型檢查,類型不一致會直接編譯報錯。 一般作為初級工程師知道這些就算合格了。
我們往深一層研究下,難道我們一定不能往聲明泛型為String的list中增加一個整型元素嗎?聰明的同學可能想到了,既然是在編譯前檢查類型的,編譯後又將類型擦除了,那我是不是可以在運行時通過反射將整型數字add進去?不錯!確實是可以的。
List<Long> list = new ArrayList<Long>(); list.add(100L); list.getClass().getMethod("add", Object.class).invoke(list, "abc"); // 然後我們將list打印出來 for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }
打印出來確實是可以的,但是我能改成下面這樣嗎?
List<Long> list = new ArrayList<Long>(); list.add(100L); list.getClass().getMethod("add", Object.class).invoke(list, "abc"); Long a = 0L; // 然後我們將list打印出來 for (int i = 0; i < list.size(); i++) { a += list.get(i); }
當然不能啦,當然如果你想的比較仔細,會發現一個很好玩的現象。對於上面這個list中第二個元素為"abc",那我們分別以不同的類型來賦值:
String s = ""; Long a = 0L; s = list.get(1); a = list.get(1);
無論你是用String還是Long類型來接收都會報錯,你用String來接收會報"Long cannot be converted to String"。你用Long類型來賦值會報"String cannot be cast to java.lang.Long",你是不是感到迷茫了,其實這一切都是因為類型擦除,對於不能使用String類型來接收是因為編譯器會做檢查,因為list聲明的泛型是Long類型的,而你使用String類型來賦值顯然編譯器會報錯,第二種你使用Long類型來接收,編譯器當然會認為是合法的,但是在運行的時候,list中的第二個實際值是String,強轉成Long當然會報錯了。這一切的根源是你使用反射向list中放入了一個和聲明不同類型的數據。正常我們一般也不會這麼做啦,這裡只是驗證一下這個機制而已。
好了,解釋了這麼多類型擦除的機制,那Java使用類型擦除來實現泛型有什麼好處呢?
1、第一點我們將如此多的泛型在編譯時擦除了,那麼在運行時顯然可以省不少的內存空間嘛。

2、第二點不得不說下兼容性,Java是在1.5版本推出的泛型,那1.5之前存在大量的線上代碼沒有泛型的,總不能捨棄吧,所以編譯擦除後和沒有泛型不是一樣嗎,這就兼容了之前更老的Java版本。
如果到這裡你基本上都會的話,我覺得完全具有中級工程師的能力了。
2
類 型 擦 除 帶 來 的 問 題
任何設計都會有自己的優點和缺點,在了解類型擦除的優點之後,我們也要剖析下類型擦除存在的現實問題:
1、不能使用基本數據類型
對於基本數據類型我們必須使用它的裝箱類,那在我們使用過程中必然會平凡的涉及到拆箱和裝箱的操作,這必定帶來一定的資源開銷,所以谷歌在針對key是int類型的情況下,使用SparseArray來代替HashMap。

2、不能用來方法的重載
為什麼呢?舉個例子:

如上圖所示,在不同的泛型作為參數時,編譯器編譯時進行類型擦除,那參數不就一樣了嗎?那還談什麼重載呢!而C#沒有進行類型擦除,所以編譯完後是帶有泛型的類型的,所以可以當作是重載的。
3、泛型類型不能當作真實的類型使用

上圖中展示了5種使用方式,除了第四種Java能正常使用,其他Java都不能使用,而C#完全沒問題。
4、靜態方法無法引用類的泛型類型

Java中的泛型是類實例化的時候才能確定泛型的準確類型,而靜態方法是不需要類實例化就能調用的,顯然不能使用。
5、類型強轉的開銷

在Java1.5之前的版本,如上圖所示,必須要進行強轉才能使用自己想要的類型。 那Java1.5及以後的版本呢?

有興趣的可以看看ArrayList的源碼,它的get方法還是會做強轉的。
如果到這裡你都知道,說明你對泛型以及類型擦除了解的還是比較系統,完全具備高工的潛質。