1. 泛型擦除的介紹
1.1 泛型擦除的原因
- 原因一:JDK1.5及1.5之前都是沒有泛型的概念的,JDK1.5之后引入了泛型的概念并為了與之前的JDK版本兼容,所以引入了泛型擦除的概念。
- 原因二:若對(duì)每個(gè)泛型類型都生成不同的目標(biāo)代碼,現(xiàn)有10個(gè)不同泛型的List,就要生成10份字節(jié)碼,這樣會(huì)造成不僅造成代碼膨脹,而且一份字節(jié)碼對(duì)應(yīng)一個(gè)Class對(duì)象,占據(jù)大量的內(nèi)存。
1.2 泛型擦除規(guī)則
-
情況一:首先將 所有聲明泛型的地方 都擦除,然后若 定義該泛型的地方 沒有指定泛型上界,則 所有該泛型類型的變量的數(shù)據(jù)類型 在編譯之后都替換為Object
-
情況二:首先將 所有聲明泛型的地方 都擦除,然后若 定義該泛型的地方 指定了泛型上界,則 所有該泛型類型的變量的數(shù)據(jù)類型 在編譯之后都替換為泛型上界
- 例題1:
![]()
- 例題2:
![]()
1.3 泛型擦除規(guī)則的驗(yàn)證
- 方式一:通過Class對(duì)象驗(yàn)證:想通過Class對(duì)象獲取泛型信息,但是僅僅獲取的泛型信息是占位符,并不是實(shí)際的泛型類型
List<Integer> list = new ArrayList<Integer>(); Map<Integer, String> map = new HashMap<Integer, String>(); System.out.println(Arrays.toString(list.getClass().getTypeParameters())); // 輸出:[E] System.out.println(Arrays.toString(map.getClass().getTypeParameters())); // 輸出:[K, V]
- 方式二:通過反射機(jī)制驗(yàn)證:我們知道泛型只是用來對(duì)變量類型進(jìn)行約束,這個(gè)約束只在編譯階段有效,在編譯之后泛型就被擦除了。比如:
因此,如果可以繞過編譯階段對(duì)泛型的約束檢測(cè),那么就可以傳入任何類型的變量(因?yàn)槎伎梢韵蛏限D(zhuǎn)型為Object類型):
運(yùn)行結(jié)果為:ArrayList<String> list = new ArrayList<>(); list.add("張三"); list.add("李四"); // 通過反射繞過編譯 Class clazz = list.getClass(); Method method = clazz.getMethod("add", Object.class); // 這里必須是Object,因?yàn)榉盒筒脸?guī)則:ArrayList<E>中E沒有泛型上界,所以泛型擦除后占位符E用Object代替 method.invoke(list, 21); // 打印該集合 System.out.println(list); System.out.println(list.get(2));
2. 使用反射獲取被擦除泛型信息的技巧
-
問:進(jìn)行泛型擦除后的程序就能夠在JDK1.5上正確執(zhí)行了,那么還有必要保存原本的泛型信息嗎?
-
答:會(huì)保存。所有類會(huì)先將自己類中涉及的
所有實(shí)際泛型
備份放在自己類中,然后自己類再將進(jìn)行泛型擦除 【超級(jí)重要!?。。。。。。。。。。。。。。。。。。。。。。。 ?/strong>。 -
原理及獲取方式:使用了泛型的類編譯為class文件時(shí)會(huì)生成一個(gè)signature字段,而原本的泛型信息就被保存在class文件中
signature
指向的常量池中。
但是signature
是一個(gè)private
修飾的屬性,不能直接訪問,只能通過反射訪問。因?yàn)閏lass文件中會(huì)生成一些public
修飾的方法能將訪問signature
屬性并獲取相關(guān)信息。所以,在實(shí)際中一般采用這些方法來獲取泛型,而不是直接使用signature
字段。具體方法為: -
例子:
class A<T, ID> { } class B extends A<String, Integer> { } public class Generic { public static void main(String[] args) { ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericSuperclass(); Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); for(Type actualTypeArgument: actualTypeArguments) { System.out.println(actualTypeArgument); } } } // 輸出: class java.lang.String class java.lang.Integer
3. 泛型擦除導(dǎo)致的兩大經(jīng)典問題的解決方案
泛型有3類:泛型類、泛型接口、泛型方法。但無論是哪種都會(huì)造成泛型擦除,而這也造成了問題:
- 問題1:由于泛型擦除,導(dǎo)致無法在
泛型類
中獲取實(shí)際泛型類型
解決方法:使用匿名內(nèi)部類 - 問題2:由于泛型擦除,導(dǎo)致無法在
泛型接口
進(jìn)行接口回調(diào)
之后獲取實(shí)際泛型類型
解決方法:使用匿名內(nèi)部類 -
泛型方法
的泛型擦除導(dǎo)致的問題,目前還沒有遇到,遇到了再補(bǔ)充?。?!
3.1 在泛型類中獲取實(shí)際泛型類型
根據(jù)泛型擦除規(guī)則知道類在編譯之后所有泛型都會(huì)被Object或者泛型上界代替,因此導(dǎo)致 泛型類 中無法獲取原本的泛型信息。
那么問題來了,我就是要獲取原本的泛型信息該怎么辦????
- 比如:要實(shí)現(xiàn)以下需求
- 有兩種方式實(shí)現(xiàn)該需求,這兩種方式都是借助反射實(shí)現(xiàn)該需求的:
- 方式一:將MyTest的class對(duì)象當(dāng)做參數(shù)傳入即可。但是這樣需要修改Stream類的代碼,也就是說要修改源碼
① 分析
② 解決方法: - 方式二:使用匿名內(nèi)部類。這樣不用修改Stream類的代碼,也就是說不需要修改源碼,只要在使用該類的地方做修改即可
-
分析:找一個(gè)類來繼承泛型類,那么這個(gè)類就能保留泛型類的實(shí)際泛型類型:
此時(shí),以上代碼從邏輯上就等價(jià)于以下代碼:
如果還不能理解,請(qǐng)運(yùn)行以下代碼并思考:class A<T, ID> { } class B extends A<String, Integer> { } public class Generic { public static void main(String[] args) { ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericSuperclass(); Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); for(Type actualTypeArgument: actualTypeArguments) { System.out.println(actualTypeArgument); } } } // 輸出: class java.lang.String class java.lang.Integer
-
最終解決方法:
class Stream<E> { public Stream(List<E> list) { ParameterizedType genericSuperclass = (ParameterizedType) this.getClass().getGenericSuperclass(); for (Type actualTypeArgument : genericSuperclass.getActualTypeArguments()) { System.out.println(actualTypeArgument); } } } public class MyTest { public static void main(String[] args) throws Exception { ArrayList<String> list = new ArrayList<>(); Stream<String> stream = new Stream<String>(list) { }; } }
-
- 方式一:將MyTest的class對(duì)象當(dāng)做參數(shù)傳入即可。但是這樣需要修改Stream類的代碼,也就是說要修改源碼
3.2 在泛型接口進(jìn)行接口回調(diào)后獲取實(shí)際泛型類型
問題:對(duì)于函數(shù)式接口,如果使用匿名內(nèi)部類創(chuàng)建該接口對(duì)象的話,一般都會(huì)使用lambda表達(dá)式來代替匿名內(nèi)部類。但是,在某些情況下匿名內(nèi)部類不會(huì)報(bào)錯(cuò),而使用lambda表達(dá)式會(huì)報(bào)錯(cuò)。
- 比如:要實(shí)現(xiàn)以下需求
- 最重要的一個(gè)點(diǎn)就是Lambda表達(dá)式是將對(duì)應(yīng)的接口改造為對(duì)應(yīng)的類,并沒有構(gòu)造新的類來實(shí)現(xiàn)泛型接口。所以泛型接口的實(shí)際泛型類型無法保存。也就是說等價(jià)于以下代碼:
- 最重要的一個(gè)點(diǎn)就是Lambda表達(dá)式是將對(duì)應(yīng)的接口改造為對(duì)應(yīng)的類,并沒有構(gòu)造新的類來實(shí)現(xiàn)泛型接口。所以泛型接口的實(shí)際泛型類型無法保存。也就是說等價(jià)于以下代碼:
- 有兩種方式實(shí)現(xiàn)該需求,這兩種方式都是借助反射實(shí)現(xiàn)該需求的:
-
方式一:將MyTest的class對(duì)象當(dāng)做參數(shù)傳入即可。但是這樣需要修改FlatMapFunc類的代碼,也就是說要修改源碼
-
方式二:使用匿名內(nèi)部類。這樣不用修改Stream類的代碼,也就是說不需要修改源碼,只要在使用該類的地方做修改即可文章來源:http://www.zghlxwxcb.cn/news/detail-817181.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-817181.html
-
到了這里,關(guān)于【Java】 泛型擦除的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!