掌握J(rèn)ava-TypeToken原理及泛型擦除
概要
借助對(duì)TypeToken原理的分析,加強(qiáng)對(duì)泛型擦除的理解,使得我們能夠知道什么時(shí)候,通過(guò)什么方式可以獲取到泛型的類(lèi)型。
泛型擦除
眾所周知,Java的泛型只在編譯時(shí)有效,到了運(yùn)行時(shí)這個(gè)泛型類(lèi)型就會(huì)被擦除掉,即List<String>和List<Integer>在運(yùn)行時(shí)其實(shí)都是List<Object>類(lèi)型。
為什么選擇這種實(shí)現(xiàn)機(jī)制?不擦除不行么?在Java誕生10年后,才想實(shí)現(xiàn)類(lèi)似于C++模板的概念,即泛型。Java的類(lèi)庫(kù)是Java生態(tài)中非常寶貴的財(cái)富,必須保證向后兼容(即現(xiàn)有的代碼和類(lèi)文件依舊合法)和遷移兼容(泛化的代碼和非泛化的代碼可互相調(diào)用)基于上面這兩個(gè)背景和考慮,Java設(shè)計(jì)者采取了“類(lèi)型擦除”這種折中的實(shí)現(xiàn)方式。
同時(shí)正正有這個(gè)這么“坑”的機(jī)制,令到我們無(wú)法在運(yùn)行期間隨心所欲的獲取到泛型參數(shù)的具體類(lèi)型。
TypeToken
使用
使用過(guò)Gson的同學(xué)都知道在反序列化時(shí)需要定義一個(gè)TypeToken類(lèi)型,像這樣
- private Type type = new TypeToken<List<Map<String, Foo>>>(){}.getType();
- //調(diào)用fromJson方法時(shí)把type傳過(guò)去,如果type的類(lèi)型和json保持一致,則可以反序列化出來(lái)
- gson.fromJson(json, type);
三個(gè)問(wèn)題
1.為什么要用TypeToken來(lái)定義反序列化的類(lèi)型?正如上面說(shuō)的,如果直接把List<Map<String, Foo>>的類(lèi)型傳過(guò)去,但是因?yàn)檫\(yùn)行時(shí)泛型被擦除了,所以得到的其實(shí)是List<Object>,那么后面的Gson就不知道要轉(zhuǎn)成Map<String, Foo>類(lèi)型了,這時(shí)Gson會(huì)默認(rèn)轉(zhuǎn)成LinkedTreeMap類(lèi)型。
2.為什么帶有大括號(hào){}?這個(gè)大括號(hào)就是精髓所在。大家都知道,在Java語(yǔ)法中,在這個(gè)語(yǔ)境,{}是用來(lái)定義匿名類(lèi),這個(gè)匿名類(lèi)是繼承了TypeToken類(lèi),它是TypeToken的子類(lèi)。
3.為什么要通過(guò)子類(lèi)來(lái)獲取泛型的類(lèi)型?這是TypeToken能夠獲取到泛型類(lèi)型的關(guān)鍵,這是一個(gè)巧妙的方法。這個(gè)想法是這樣子的,既然像List<String>這樣中的泛型會(huì)被擦除掉,那么我用一個(gè)子類(lèi)SubList extends List<String>這樣的話(huà),在JVM內(nèi)部中會(huì)不會(huì)把父類(lèi)泛型的類(lèi)型給保存下來(lái)呢?
我這個(gè)子類(lèi)需要繼承的父類(lèi)的泛型都是已經(jīng)確定了的呀,果然,JVM是有保存這部分信息的,它是保存在子類(lèi)的Class信息中。
具體看:
https://stackoverflow.com/questions/937933/where-are-generic-types-stored-in-java-class-files
那么我們?cè)趺传@取這部分信息呢?還好,Java有提供API出來(lái):
- Type mySuperClass = foo.getClass().getGenericSuperclass();
- Type type = ((ParameterizedType)mySuperClass).getActualTypeArguments()[0];
- System.out.println(type);
分析一下這段代碼,Class類(lèi)的getGenericSuperClass()方法的注釋是:
Returns the Type representing the direct superclass of the entity (class, interface, primitive type or void) represented by thisClass. If the superclass is a parameterized type, the Type object returned must accurately reflect the actual type parameters used in the source code. The parameterized type representing the superclass is created if it had not been created before. See the declaration of ParameterizedType for the semantics of the creation process for parameterized types. If thisClass represents either theObject class, an interface, a primitive type, or void, then null is returned. If this object represents an array class then theClass object representing theObject class is returned
概括來(lái)說(shuō)就是對(duì)于帶有泛型的class,返回一個(gè)ParameterizedType對(duì)象,對(duì)于Object、接口和原始類(lèi)型返回null,對(duì)于數(shù) 組class則是返回Object.class。ParameterizedType是表示帶有泛型參數(shù)的類(lèi)型的Java類(lèi)型,JDK1.5引入了泛型之 后,Java中所有的Class都實(shí)現(xiàn)了Type接口,ParameterizedType則是繼承了Type接口,所有包含泛型的Class類(lèi)都會(huì)實(shí)現(xiàn) 這個(gè)接口。
自己調(diào)試一下就知道它返回的是什么了。
原理
核心的方法就是剛剛說(shuō)的那兩句,剩下的就很簡(jiǎn)單了。我們看看TypeToken的getType方法
- public final Type getType() {
- //直接返回type
- return type;
- }
看type的初始化
- //注意這里用了protected關(guān)鍵字,限制了只有子類(lèi)才能訪問(wèn)
- protected TypeToken() {
- this.type = getSuperclassTypeParameter(getClass());
- this.rawType = (Class<? super T>) $Gson$Types.getRawType(type);
- this.hashCode = type.hashCode();
- }
- //getSuperclassTypeParameter方法
- //這幾句就是上面的說(shuō)到
- static Type getSuperclassTypeParameter(Class<?> subclass) {
- Type superclass = subclass.getGenericSuperclass();
- if (superclass instanceof Class) {
- throw new RuntimeException("Missing type parameter.");
- }
- ParameterizedType parameterized = (ParameterizedType) superclass;
- //這里注意一下,返回的是Gson自定義的,在$Gson$Types里面定義的TypeImpl等,這個(gè)類(lèi)都是繼承Type的。
- return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
- }
總結(jié)
在了解原理之后,相信大家都知道怎么去獲取泛型的類(lèi)型了。