泛型的類型擦除后fastjson反序列化時如何還原詳解
鋪墊
在前面的文章中,我們講過Java中泛型的類型擦除,不過有小伙伴在后臺留言提出了一個問題,帶有泛型的實體的反序列化過程是如何實現(xiàn)的,今天我們就來看看這個問題。
我們選擇fastjson
來進行反序列化的測試,在測試前先定義一個實體類:
@Data public class Foo<T> { private String val; private T obj; }
如果大家對泛型的類型擦除比較熟悉的話,就會知道在編譯完成后,其實在類中是沒有泛型的。我們還是用Jad
反編譯一下字節(jié)碼文件,可以看到?jīng)]有類型限制的T
會被直接替換為Object
類型:
下面使用fastjson
進行反序列化,先不指定Foo
中泛型的類型:
public static void main(String[] args) { String jsonStr = "{\"obj\":{\"name\":\"Hydra\",\"age\":\"18\"},\"val\":\"str\"}"; Foo<?> foo = JSONObject.parseObject(jsonStr, Foo.class); System.out.println(foo.toString()); System.out.println(foo.getObj().getClass()); }
查看執(zhí)行結(jié)果,很明顯fastjson
不知道要把obj
里的內(nèi)容反序列化成我們自定義的User
類型,于是將它解析成了JSONObject
類型的對象。
Foo(val=str, obj={"name":"Hydra","age":"18"}) class com.alibaba.fastjson.JSONObject
那么,如果想把obj
的內(nèi)容映射為User
實體對象應該怎么寫呢?下面先來示范幾種錯誤寫法。
錯誤寫法1
嘗試在反序列化時,直接指定Foo
中的泛型為User
:
Foo<User> foo = JSONObject.parseObject(jsonStr, Foo.class); System.out.println(foo.toString()); System.out.println(foo.getObj().getClass());
結(jié)果會報類型轉(zhuǎn)換的錯誤,JSONObject
不能轉(zhuǎn)成我們自定義的User
:
Exception in thread "main" java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.hydra.json.model.User
at com.hydra.json.generic.Test1.main(Test1.java:24)
錯誤寫法2
再試試使用強制類型轉(zhuǎn)換:
Foo<?> foo =(Foo<User>) JSONObject.parseObject(jsonStr, Foo.class); System.out.println(foo.toString()); System.out.println(foo.getObj().getClass());
執(zhí)行結(jié)果如下,可以看到,泛型的強制類型轉(zhuǎn)換雖然不會報錯,但是同樣也沒有生效。
Foo(val=str, obj={"name":"Hydra","age":"18"}) class com.alibaba.fastjson.JSONObject
好了,現(xiàn)在請大家忘記上面這兩種錯誤的使用方法,代碼中千萬別這么寫,下面我們看正確的寫法。
正確寫法
在使用fastjson
時,可以借助TypeReference
完成指定泛型的反序列化:
public class TypeRefTest { public static void main(String[] args) { String jsonStr = "{\"obj\":{\"name\":\"Hydra\",\"age\":\"18\"},\"val\":\"str\"}"; Foo foo2 = JSONObject.parseObject(jsonStr, new TypeReference<Foo<User>>(){}); System.out.println(foo2.toString()); System.out.println(foo2.getObj().getClass()); } }
運行結(jié)果:
Foo(val=str, obj=User(name=Hydra, age=18)) class com.hydra.json.model.User
Foo
中的obj
類型為User
,符合我們的預期。下面我們就看看,fastjson
是如何借助TypeReference
完成的泛型類型擦除后的還原。
TypeReference
回頭再看一眼上面的代碼中的這句:
Foo foo2 = JSONObject.parseObject(jsonStr, new TypeReference<Foo<User>>(){});
重點是parseObject
方法中的第二個參數(shù),注意在TypeReference<Foo<User>>()
有一對大括號{}
。也就是說這里創(chuàng)建了一個繼承了TypeReference
的匿名類的對象,在編譯完成后的項目target
目錄下,可以找到一個TypeRefTest$1.class
字節(jié)碼文件,因為匿名類的命名規(guī)則就是主類名+$+(1,2,3……)
。
反編譯這個文件可以看到這個繼承了TypeReference
的子類:
static class TypeRefTest$1 extends TypeReference { TypeRefTest$1() { } }
我們知道,在創(chuàng)建子類的對象時,子類會默認先調(diào)用父類的無參構(gòu)造方法,所以看一下TypeReference
的構(gòu)造方法:
protected TypeReference(){ Type superClass = getClass().getGenericSuperclass(); Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; Type cachedType = classTypeCache.get(type); if (cachedType == null) { classTypeCache.putIfAbsent(type, type); cachedType = classTypeCache.get(type); } this.type = cachedType; }
其實重點也就是前兩行代碼,先看第一行:
Type superClass = getClass().getGenericSuperclass();
雖然這里是在父類中執(zhí)行的代碼,但是getClass()
得到的一定是子類的Class對象,因為getClass
()方法獲取到的是當前運行的實例自身的Class,不會因為調(diào)用位置改變,所以getClass()
得到的一定是TypeRefTest$1
。
獲取當前對象的Class后,再執(zhí)行了getGenericSuperclass()
方法,這個方法與getSuperclass
類似,都會返回直接繼承的父類。不同的是getSuperclas
沒有返回泛型參數(shù),而getGenericSuperclass
則返回了包含了泛型參數(shù)的父類。
再看第二行代碼:
Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
首先將上一步獲得的Type
強制類型轉(zhuǎn)換為ParameterizedType
參數(shù)化類型,它是泛型的一個接口,實例則是繼承了它的ParameterizedTypeImpl
類的對象。
在ParameterizedType
中定義了三個方法,上面代碼中調(diào)用的getActualTypeArguments()
方法就用來返回泛型類型的數(shù)組,可能返回有多個泛型,這里的[0]
就是取出了數(shù)組中的第一個元素。
驗證
好了,明白了上面的代碼的作用后,讓我們通過debug來驗證一下上面的過程,執(zhí)行上面TypeRefTest
的代碼,查看斷點中的數(shù)據(jù):
這里發(fā)現(xiàn)一點問題,按照我們上面的分析,講道理這里父類TypeReference
的泛型應該是Foo<User>
啊,為什么會出現(xiàn)一個List<String>
?
別著急,讓我們接著往下看,如果你在TypeReference
的無參構(gòu)造方法中加了斷點,就會發(fā)現(xiàn)代碼執(zhí)行中會再調(diào)用一次這個構(gòu)造方法。
好了,這次的結(jié)果和我們的預期相同,父類的泛型數(shù)組中存儲了Foo<User>
,也就是說其實TypeRefTest$1
繼承的父類,完成的來說應該是TypeReference<Foo<User>>
,但是我們上面反編譯的文件中因為擦除的原因沒有顯示。
那么還有一個問題,為什么這個構(gòu)造方法會被調(diào)用了兩次呢?
看完了TypeReference
的代碼,終于在代碼的最后一行讓我發(fā)現(xiàn)了原因,原來是在這里先創(chuàng)建了一個TypeReference
匿名類對象!
public final static Type LIST_STRING = new TypeReference<List<String>>() {}.getType();
因此整段代碼執(zhí)行的順序是這樣的:
- 先執(zhí)行父類中靜態(tài)成員變量的定義,在這里聲明并實例化了這個
LIST_STRING
,所以會執(zhí)行一次TypeReference()
構(gòu)造方法,這個過程對應上面的第一張圖 - 然后在實例化子類的對象時,會再執(zhí)行一次父類的構(gòu)造方法
TypeReference()
,對應上面的第二張圖 - 最后執(zhí)行子類的空構(gòu)造方法,什么都沒有干
至于在這里聲明的LIST_STRING
,在其他地方也沒有被再使用過,Hydra也不知道這行代碼的意義是什么,有明白的小伙伴可以留言告訴我。
這里在拿到了Foo
中的泛型User
后,后面就可以按照這個類型來反序列化了,對后續(xù)流程有興趣的小伙伴可以自己去啃啃源碼,這里就不展開了。
擴展
了解了上面的過程后,我們最后通過一個例子加深一下理解,以常用的HashMap
作為例子:
public static void main(String[] args) { HashMap<String,Integer> map=new HashMap<String,Integer>(); System.out.println(map.getClass().getSuperclass()); System.out.println(map.getClass().getGenericSuperclass()); Type[] types = ((ParameterizedType) map.getClass().getGenericSuperclass()) .getActualTypeArguments(); for (Type t : types) { System.out.println(t); } }
執(zhí)行結(jié)果如下,可以看到這里取到的父類是HashMap
的父類AbstractMap
,并且取不到實際的泛型類型。
class java.util.AbstractMap java.util.AbstractMap<K, V> K V
修改上面的代碼,僅做一點小改動:
public static void main(String[] args) { HashMap<String,Integer> map=new HashMap<String,Integer>(){}; System.out.println(map.getClass().getSuperclass()); System.out.println(map.getClass().getGenericSuperclass()); Type[] types = ((ParameterizedType) map.getClass().getGenericSuperclass()) .getActualTypeArguments(); for (Type t : types) { System.out.println(t); } }
執(zhí)行結(jié)果大有不同,可以看到,只是在new HashMap<String,Integer>()
的后面加了一對大括號{}
,就可以取到泛型的類型了:
class java.util.HashMap java.util.HashMap<java.lang.String, java.lang.Integer> class java.lang.String class java.lang.Integer
因為這里實例化的是一個繼承了HashMap
的匿名內(nèi)部類的對象,因此取到的父類就是HashMap
,并可以獲取到父類的泛型類型。
其實也可以再換一個寫法,把這個匿名內(nèi)部類換成顯示聲明的非匿名的內(nèi)部類,再修改一下上面的代碼:
public class MapTest3 { static class MyMap extends HashMap<String,Integer>{} public static void main(String[] args) { MyMap myMap=new MyMap(); System.out.println(myMap.getClass().getSuperclass()); System.out.println(myMap.getClass().getGenericSuperclass()); Type[] types = ((ParameterizedType) myMap.getClass().getGenericSuperclass()) .getActualTypeArguments(); for (Type t : types) { System.out.println(t); } } }
運行結(jié)果與上面完全相同:
class java.util.HashMap java.util.HashMap<java.lang.String, java.lang.Integer> class java.lang.String class java.lang.Integer
唯一不同的是顯式生成的內(nèi)部類與匿名類命名規(guī)則不同,這里生成的字節(jié)碼文件不是MapTest3$1.class
,而是MapTest3$MyMap.class
,在$
符后面使用的是我們定義的類名。
以上就是泛型的類型擦除后fastjson反序列化時如何還原詳解的詳細內(nèi)容,更多關(guān)于fastjson反序列化還原的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring MVC登錄注冊以及轉(zhuǎn)換json數(shù)據(jù)
本文主要介紹了Spring MVC登錄注冊以及轉(zhuǎn)換json數(shù)據(jù)的相關(guān)知識。具有很好的參考價值。下面跟著小編一起來看下吧2017-04-04淺談Java如何實現(xiàn)一個基于LRU時間復雜度為O(1)的緩存
這篇文章主要介紹了淺談Java如何實現(xiàn)一個基于LRU時間復雜度為O(1)的緩存,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-08-08教你1秒將本地SpringBoot項目jar包部署到Linux環(huán)境(超詳細!)
spring Boot簡化了Spring應用的開發(fā)過程,遵循約定優(yōu)先配置的原則提供了各類開箱即用(out-of-the-box)的框架配置,下面這篇文章主要給大家介紹了關(guān)于1秒將本地SpringBoot項目jar包部署到Linux環(huán)境的相關(guān)資料,超級詳細,需要的朋友可以參考下2023-04-04Springboot項目通過redis實現(xiàn)接口的冪等性
這篇文章主要為大家介紹了Springboot項目通過redis實現(xiàn)接口的冪等性,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12微服務架構(gòu)設計RocketMQ進階事務消息原理詳解
這篇文章主要介紹了為大家介紹了微服務架構(gòu)中RocketMQ進階層面事務消息的原理詳解,有需要的朋友可以借鑒參考下希望能夠有所幫助2021-10-10Springdoc替換swagger的實現(xiàn)步驟分解
最近在spring看到的,spring要對api文檔動手了,有些人說swagger不好用,其實也沒那么不好用,有人說代碼還是有點侵入性,這倒是真的,我剛試了springdoc可以說還是有侵入性但是也可以沒有侵入性,這就看你對文檔有什么要求了2023-02-02