最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

泛型的類型擦除后fastjson反序列化時如何還原詳解

 更新時間:2022年11月09日 16:58:40   作者:碼農(nóng)參上  
這篇文章主要為大家介紹了泛型的類型擦除后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實體對象應(yīng)該怎么寫呢?下面先來示范幾種錯誤寫法。

錯誤寫法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,符合我們的預(yù)期。下面我們就看看,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的泛型應(yīng)該是Foo<User>啊,為什么會出現(xiàn)一個List<String>?

別著急,讓我們接著往下看,如果你在TypeReference的無參構(gòu)造方法中加了斷點,就會發(fā)現(xiàn)代碼執(zhí)行中會再調(diào)用一次這個構(gòu)造方法。

好了,這次的結(jié)果和我們的預(yù)期相同,父類的泛型數(shù)組中存儲了Foo<User>,也就是說其實TypeRefTest$1繼承的父類,完成的來說應(yīng)該是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)造方法,這個過程對應(yīng)上面的第一張圖
  • 然后在實例化子類的對象時,會再執(zhí)行一次父類的構(gòu)造方法TypeReference(),對應(yīng)上面的第二張圖
  • 最后執(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)文章

  • Java?API操作Hdfs的示例詳解

    Java?API操作Hdfs的示例詳解

    這篇文章主要介紹了Java?API操作Hdfs詳細示例,遍歷當前目錄下所有文件與文件夾,可以使用listStatus方法實現(xiàn)上述需求,本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2022-08-08
  • Idea Project文件目錄不見了,只剩External Libraries和imi文件的解決

    Idea Project文件目錄不見了,只剩External Libraries和imi文件的解決

    這篇文章主要介紹了Idea Project文件目錄不見了,只剩External Libraries和imi文件的解決方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • JAVA Spring Boot 自動配置實現(xiàn)原理詳解

    JAVA Spring Boot 自動配置實現(xiàn)原理詳解

    這篇文章主要介紹了詳解SpringBoot自動配置原理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2021-09-09
  • SpringSecurity注銷設(shè)置的方法

    SpringSecurity注銷設(shè)置的方法

    這篇文章主要為大家詳細介紹了SpringSecurity注銷設(shè)置的方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • 淺談springMVC接收前端json數(shù)據(jù)的總結(jié)

    淺談springMVC接收前端json數(shù)據(jù)的總結(jié)

    下面小編就為大家分享一篇淺談springMVC接收前端json數(shù)據(jù)的總結(jié),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • IDEA中創(chuàng)建maven項目webapp目錄無法識別即未被標識的解決辦法

    IDEA中創(chuàng)建maven項目webapp目錄無法識別即未被標識的解決辦法

    在學(xué)習(xí)SpringMVC課程中,基于IDEA新建maven項目模塊后,webapp目錄未被標識,即沒有小藍點的圖標顯示,所以本文給大家介紹了IDEA中創(chuàng)建maven項目webapp目錄無法識別即未被標識的解決辦法,需要的朋友可以參考下
    2024-03-03
  • Struts 2中的constant配置詳解

    Struts 2中的constant配置詳解

    通過對這些屬性的配置,可以改變Struts 2 框架的一些默認行為,這些配置可以在struts.xml文件中完成,也可以在struts.properties文件中完成
    2016-09-09
  • 詳解springboot 使用c3p0數(shù)據(jù)庫連接池的方法

    詳解springboot 使用c3p0數(shù)據(jù)庫連接池的方法

    本篇文章主要介紹了springboot 使用c3p0數(shù)據(jù)庫連接池的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-09-09
  • 使用Pinyin4j進行拼音分詞的方法

    使用Pinyin4j進行拼音分詞的方法

    下面小編就為大家分享一篇使用Pinyin4j進行拼音分詞的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-01-01
  • Java類的初始化實踐分析

    Java類的初始化實踐分析

    這篇文章主要介紹了Java類的初始化,結(jié)合具體實例形式較為詳細的分析了java類初始化的常見情況、操作技巧與相關(guān)注意事項,需要的朋友可以參考下
    2019-07-07

最新評論

潞城市| 姜堰市| 原阳县| 莱西市| 庆云县| 巴塘县| 泾川县| 侯马市| 温州市| 南召县| 南开区| 历史| 康保县| 舟山市| 潜山县| 大竹县| 桐梓县| 新龙县| 湖北省| 郯城县| 政和县| 新干县| 高安市| 新余市| 靖宇县| 利津县| 宁城县| 米林县| 璧山县| 涟水县| 琼中| 老河口市| 七台河市| 赤水市| 罗山县| 晋州市| 汨罗市| 喀什市| 涡阳县| 兴仁县| 盐山县|