版權申明】非商業目的註明出處可自由轉載
博文地址:https://blog.csdn.net/ShuSheng0007/article/details/89789849
出自:shusheng007
相關文章:
秒懂Java泛型
秒懂Java類型(Type)系統
概述
曾幾何時你是否在簡歷中寫道:精通Java、精通xx、精通oo 等等的字眼,說出來不怕丟人,本人也幹過。現在想想真是驚歎於當時的“勇氣”,或者是自大?亦或是無知? 如果爲了找工作,寫上這些還是情有可原的,畢竟大部分篩選簡歷的是那些毛都不懂的HR,但是你內心中還是要對精通二字懷有敬畏之情!不爲別的,就爲了你可以虛懷若谷,勇往直前,如果你都認爲自己精通了,估計就會坐井觀天,止步不前了。
今天讓我們聊聊Java泛型擦除那些事,就當做自己的學習筆記吧。
泛型
概念及使用方法不是本文的重點,如果對泛型的概念和使用有疑問請出門左轉查看 秒懂Java泛型。本文也不想爭論Java泛型實現機制的優劣,因爲研究不夠深入,不敢妄言。我們就現在Java泛型的實現方式聊聊其中的一些事。
泛型信息運行時擦除
Java 泛型在運行時擦除這一點應該大部分開發者都有一定了的解,那麼什麼叫擦除了呢?就是泛型信息在源代碼裏面有,而在生成的字節碼裏面就沒有了,之所以這麼幹主要是爲了向下兼容老的java版本。那問題就很明瞭了,字節碼中都不存在泛型的信息了,而我們的運行時對象是JVM
從字節碼裝載進來的,那自然也就沒有泛型信息了。看下面代碼
ArrayList<String> arrayString=new ArrayList<String>();
ArrayList<Integer> arrayInteger=new ArrayList<Integer>();
System.out.println(arrayString.getClass()==arrayInteger.getClass());
上面代碼輸入結果爲 true,可見通過運行時獲取的類信息是完全一致的,泛型類型被擦除了!
至此,我以爲我精通了Java 泛型,直到有一天我寫了 秒懂Java類型(Type)系統 這篇文章,我變的很困惑。不是說Java的泛型是運行時擦除的嗎,爲什麼我還能通過反射獲取到泛型信息,例如我有如下代碼
public class TypeTest<T>{
public List<T> list;
}
我通過反射可以獲取到這list的聲明類型 java.util.List<T>
Field list = TypeTest.class.getField("list");
Type genericType1 = list.getGenericType();
System.out.println("參數類型1:" + genericType1.getTypeName());
輸出
參數類型1:java.util.List<T>
是不是很奇怪,不是被擦除了嗎?這是爲什麼?
泛型信息聲明時保留
通過仔細思考上面對泛型擦除的原理的理解:泛型擦除是泛型信息在源代碼裏面有,而在生成的字節碼裏面就沒有了,我們會提出疑問。難道這種情況下,泛型的信息被保留到了字節碼裏面去了嗎?如果你對一個問題有疑問那就去親自試一下。
第一:定義一個非泛型類,然後查看其生成的字節碼文件
public class FruitsContainer{
public Number mNum;
}
我用 Bytecode Viewer 這個工具來查看生成的字節碼文件FruitsContainer.class
,如下圖所示:第一部分是源代碼,第二部分是從class
文件中反編譯出的代碼,第三部分是字節碼文件內容。
我們其實要重點關注的是字節碼的那部分,看與其泛型版本的異同
第二:定義一個泛型類,然後查看其生成的字節碼文件
public class Fruit {
}
public class FruitsContainer<T extends Fruit>{
public T t;
}
如下圖所示:
我們先看最右側的原始的字節碼文件有何異同。可以看到,在第三行多了&<T:Ladvanced/Fruit:>
,下面還有幾處帶<>
的地方,這些就是被保留了的泛型信息。其實從第二部分反編譯後的源碼更容易看出,多了很多Signature
.
可見,在這種情況下,泛型的信息被保留到了字節碼文件中。
結論
關於Java泛型有兩種請求:
- 聲明側泛型信息保留
例如聲明泛型接口,泛型類,泛型方法時的泛型信息 - 使用側泛型信息擦除
例如方法的局部變量等。
獲取並使用泛型
下面舉一個如何獲取並使用泛型信息的例子,接着上面的代碼,我們定義如下兩個類
public class Apple extends Fruit {
}
public class AppleContainer extends FruitsContainer<Apple> {
public AppleContainer appleContainer;
}
其中AppleContainer
繼承至FruitsContainer
泛型類.我們可以通過如下代碼獲得AppleContainer
帶泛型信息的父類FruitsContainer<advanced.Apple>
private static void genericTest(){
Type superType=(AppleContainer.class.getGenericSuperclass());
System.out.println(superType);
if (superType instanceof ParameterizedType){
ParameterizedType paramType=(ParameterizedType)superType;
System.out.println(Arrays.toString(paramType.getActualTypeArguments()));
}
}
輸出:
advanced.FruitsContainer<advanced.Apple>
[class advanced.Apple]
可見,我們是有能力在運行時獲取聲明類型的泛型信息的。
使用場景
那麼在運行時獲取泛型信息有什麼用呢?我現在遇到過兩個使用場景。
- 對泛型類型進行校驗
在Retrofit2
這個網絡請求庫中大量使用,例如其要求定義的方法返回類型必須是泛型的,例如Call<T>
,而不能是Call
。它還會校驗T
是否爲合法的類型. - 序列化和反序列化
例如使用Gson時,我們需要將·json·轉換爲Java對象,假設這個對象是帶泛型信息,就得告訴框架要轉換的那個java對象的泛型類型。
List<CommentBean>= fromJson("json...",
new TypeToken<ArrayList<CommentBean>>(){}.getType());
上面那段反序列化的代碼就是要告訴Gson框架泛型參數爲ArrayList<CommentBean>>
。這裏面又有一個很有趣的點,就是 new TypeToken<ArrayList<CommentBean>>(){}
而不是 new TypeToken<ArrayList<CommentBean>>()
加上一對{}
就表示是匿名類,那樣通過編譯器編譯後才能正確獲取到其泛型信息。
例如我們有如下代碼,那麼大家想一想,輸出是什麼呢?
System.out.println(new FruitsContainer<Apple>().getClass().getGenericSuperclass());
System.out.println(new FruitsContainer<Apple>(){}.getClass().getGenericSuperclass());
輸出:
class java.lang.Object
advanced.FruitsContainer<advanced.Apple>
留個思考題,爲什麼會這樣?有興趣的可以在評論區討論,其實上面已經給出了提示。
總結
如果你仍然在做技術,那就努力提高你的技術,雖然真理無窮。但怕什麼真理無窮,進一寸有一寸的歡喜!
最後,求關注,求點贊!有任何疑問可以評論留言,我會盡力回覆的。