Java基础篇:泛型与类型擦除
一、什么是泛型;
泛型的本質是 參數化類型,也就是說 將所操作的數據類型 指定為一個參數,在不創建新類的情況下,通過參數來指定所要操作的具體類型(類似于方法中的變量參數,此時類型也定義成參數形式),也就是說,在創建對象或者調用方法的時候才明確下具體的類型。可以在類、接口、方法中使用,分別稱為泛型類、泛型接口、泛型方法。
二、泛型的好處:
沒有泛型的情況的下,通過對類型Object的引用來實現參數的“任意化”,“任意化”帶來的缺點是要做顯式的強制類型轉換,而這種轉換是要求開發者對實際參數類型可以預知的情況下進行的。
而引入泛型后,有如下好處:
1、消除顯式的強制類型轉換,提高代碼可讀性:
泛型中,所有的類型轉換都是自動和隱式的,不需要強制類型轉換,可以提高代碼的重用率,再加上明確的類型信息,代碼的可讀性也會更好。
2、編譯時的類型檢查,使程序更加健壯:
對于強制類型轉換錯誤的情況,編譯期不會提示錯誤,在運行的時候才出現異常,這是一個安全隱患。泛型的好處是在編譯期檢查類型安全,并能捕捉類型不匹配的錯誤,避免運行時拋出類型轉化異常ClassCastException,將運行時錯誤提前到編譯時錯誤,消除安全隱患。
三、Java類庫中的泛型有那些?泛型的用途?
(1)泛型類:最常見的用途就是容器類,通過泛型可以完成對一組類的操作對外開放相同的接口。所有的標準集合接口都是泛型化的---Collection<V>、List<V>、Set<V> 和 Map<K,V>。
(2)泛型接口:類似地,集合接口的實現都是用相同類型參數泛型化的,所以HashMap<K,V> 實現 Map<K,V> 等都是泛型的,Comparable和Comparator接口也是泛型的。
除了集合類之外,Java 類庫中還有幾個其他的類也充當值的容器。這些類包括 WeakReference、SoftReference 和 ThreadLocal。
(3)泛型方法:要定義泛型方法,只需將泛型參數列表置于返回值之前。
靜態方法上的泛型:靜態方法無法訪問類上定義的泛型。如果靜態方法操作的引用數據類型不確定的時候,必須要將泛型定義在方法上。如下:
public static<Q> void function(Q t) {System.out.println("function:"+t); }四、泛型的上界下界:
<?extends T> 表示類型的上界,參數化類型可能是T 或者是 T的子類;
<? super T> 表示類型的下界,參數化類型是此T類型的超類型,直至object;
上界什么時候用:往集合中添加元素時,既可以添加T類型對象,又可以添加T的子類型對象。為什么?因為存的時候,T類型既可以接收T類對象,又可以接收T的子類型對象。
下界什么時候用:當從集合中獲取元素進行操作的時候,可以用當前元素的類型接收,也可以用當前元素的父類型接收。
五、Java泛型的實現方法--類型擦除:
Java泛型的實現是靠類型擦除技術實現的,類型擦除是在編譯期完成的,也就是在編譯期,編譯器會將泛型的類型參數都擦除成它指定的原始限定類型,如果沒有指定的原始限定類型則擦除為object類型,之后在獲取的時候再強制類型轉換為對應的類型,因此生成的Java字節碼中是不包含泛型中的類型信息的,即運行期間并沒有泛型的任何信息。
下圖參考博客:https://blog.csdn.net/shinecjj/article/details/52075499
(1)在使用泛型的時候,雖然傳入了不同的泛型實參,但并沒有真正意義上生成不同的類型,傳入不同泛型實參的泛型類在內存中只有一個,即還是原來的最基本的類型;泛型只在編譯階段有效,在編譯過程中,對于正確檢驗泛型結果后,會將泛型的相關信息擦除,并且在對象進入和離開方法的邊界處添加類型檢查和類型轉化的方法,也就是說,成功編譯后的class文件是不包含任何泛型信息的。總結成一句話:泛型類型在邏輯上看以看成是多個不同的類型,實際上都是相同類型。
(2)因此,泛型類型在邏輯上可以看成是多個不同的類型,但實際上都是相同的基本類型。類型參數在運行中并不存在,這意味著他們不會添加任何的時間和空間上的負擔;但是,這也意味著不能依靠他們進行類型轉換。
舉兩個例子說明一下類型擦除:
5.1、類型擦除:
public?class?Test4?{??public?static?void?main(String[]?args)?{??ArrayList<String>?arrayList1=new?ArrayList<String>();??arrayList1.add("abc");??ArrayList<Integer>?arrayList2=new?ArrayList<Integer>();??arrayList2.add(123);??System.out.println(arrayList1.getClass()==arrayList2.getClass());??//true}??}??在這個例子中,我們定義了兩個ArrayList數組,不過一個是ArrayList<String>泛型類型,只能存儲字符串。一個是ArrayList<Integer>泛型類型,只能存儲整形。最后,我們通過arrayList1對象和arrayList2對象的getClass方法獲取它們的類的信息,最后發現結果為true。說明泛型類型String和Integer都被擦除掉了,只剩下了原始類型。
5.2、轉型和instanceof :
//泛型類被所有實例(instances)共享的另一個暗示是檢查一個特定類型的泛型類是沒有意義的。 Collection cs = new ArrayList<String>(); if (cs instanceof Collection<String>) { ...} // 非法類似的,如下的類型轉換 Collection<String> cstr = (Collection<String>) cs; 得到一個unchecked warning,因為運行時環境不會為你作這樣的檢查。六、類型擦除帶來的問題:
詳細參考這篇博客,博主介紹的很詳細了:https://blog.csdn.net/LonelyRoamer/article/details/7868820
七、關于泛型的其他一些小細節:
1、可以創建泛型數組嗎?相應的應用場景怎么處理?
? ? ?不能創建泛型數組。一般的解決方案是任何想要創建泛型數組的地方都使用ArrayList?
2、可以將基本類型作為泛型參數嗎?
? ? ?泛型的類型參數只能是類類型(包括自定義類),不能是簡單類型(基本數據類型)。
3、什么時候用泛型?
? ? ?當接口、類及方法中的操作的引用數據類型不確定的時候,以前用的Object來進行擴展的,現在可以用泛型來表示。這樣可以避免強轉的麻煩,而且將運行問題轉移到的編譯時期。
4、泛型的細節:
(1)泛型實際代表什么類型,取決于調用者傳入的類型,如果沒傳,默認是Object類型;
(2)使用帶泛型的類創建對象時,等式兩邊指定的泛型類型必須一致。
? ? ? ? 原因:編譯器檢查對象調用方法時只看變量,然而程序在運行期間調用方法時就要考慮對象具體類型了。
(3)等式兩邊可以在任意一邊使用泛型,在另一邊不使用(考慮向后兼容);
ArrayList<String>al = new ArrayList<Object>();? //錯 //要保證左右兩邊的泛型具體類型一致就可以了,這樣不容易出錯。 ArrayList<?extends Object> al = new ArrayList<String>(); al.add("aa");? //錯 //因為集合具體對象中既可存儲String,也可以存儲Object的其他子類,所以添加具體的類型對象不合適,類型檢查會出現安全問題。推薦閱讀:
https://juejin.cn/post/6911113640158593032
參考博客:
https://blog.csdn.net/s10461/article/details/53941091
https://blog.csdn.net/Jinuxwu/article/details/6771121
https://blog.csdn.net/caihuangshi/article/details/51278793
總結
以上是生活随笔為你收集整理的Java基础篇:泛型与类型擦除的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java基础篇:抽象类与接口
- 下一篇: Java基础篇:回调机制详解