Json反序列化与Java泛型
Java的JSON庫有很多,本文分析google的Gson和alibaba的fastjson,在Java泛型場景反序列化的一些有意思的行為。考慮下面的json字符串:
["2147483648","2147483647" ]用fastjson在不指定類型的情況下解析,下面的代碼輸出啥:
JSON.parseArray(s).forEach(o -> { System.out.println(o.getClass()); });答案是:
class java.lang.Long class java.lang.Integer是不是感覺有點兒奇怪,兩個都是數字啊,居然輸出了不同的類型,原因我們下面細講。再看看Gson, 用Gson解析并且不指定泛型類型的話,下面的代碼輸出啥:
new Gson().fromJson(s, List.class).forEach(o -> { System.out.println(o.getClass()); });答案是:
class java.lang.Double class java.lang.Double這次兩個都是Double類型,明明是整數啊,為啥到Gson這里變成Double了?
我們試了兩個Json庫,解析相同的json字符串,得到全然不同的結果。如果在實際使用的時候,用這種方式反序列化JSON,很容易出現BUG,而且是運行時才可能出現,這種問題一旦出現往往很難排查。因此為了保證泛型類型Json反序列化的正確性,一定要明確指定泛型的類型。下面我們先看看正確的解析應該怎么寫,再深度探討一下內部的原理。
正確的泛型Json反序列化
fastjson和Gson都提供了泛型類型的反序列化方案,先來看看fastjson,對于上面的case,正確的反序列化代碼如下:
JSON.parseObject(s, new TypeReference<List<Long>>(){}) .forEach(o -> { System.out.println(o.getClass()); });創建一個確定泛型類型的TypeReference子類(這里是匿名內部類),將這個子類傳遞給fastjson以幫助fastjson在運行時獲得泛型的具體類型信息,從而實現泛型正確反序列化。Gson的方案與fastjson相同,或者應該反過來說fastjson的方案與Gson相同。大家感興趣的話可以看看fastjson的TypeReference和Gson的TypeToken代碼,基本上fastjson就是抄襲Gson,連注釋都抄了......。Gson指定泛型類型的反序列化方法如下,也是創建一個確定泛型類型的匿名子類:
new Gson().<List<Long>>fromJson(s, new TypeToken<List<Long>>(){}.getType()) .forEach(o -> { System.out.println(o.getClass()); });由于fastjson的方案是來自Gson,以下只討論Gson的泛型反序列化原理。為什么泛型的反序列化顯得這么麻煩呢,非要通過子類化的方式,不能直接告訴Gson泛型的類型嗎?是的,Java是真的做不到,其實理由很簡單,泛型類既然是泛型,意味著就不應該帶有具體泛型類型的信息,因此泛型類的字節碼本身就不應該也無法保存泛型類型,但是子類如果繼承一個明確泛型類型的父類(父類是一個泛型類型,Gson里面就是TypeToken),子類必須保存父類的明確類型信息,通過Class類的getGenericSuperclass方法能夠獲得父類類型信息,該類型信息包含了父類具體的泛型類型信息。這個方法幫助Java的泛型體系完整化了,是非常重要的一個方法。
類庫設計分析
面對相同的設計問題,fastjson與Gson在很多點上的選擇不同,借此機會可以一窺類庫設計的思想,讓我們一一來看。
是靜態方法還是實例化
使用Gson之前,必須進行實例化,Gson提供了兩種方式:一種是無參數構造器,一種是通過GsonBuilder,后者能夠進行更多的定制,但無論是哪種方法,都需要實例化一個Gson對象。但是Fastjson使用之前是不需要實例化的,直接使用JSON類的靜態方法即可實現json序列化和反序列化。這一點上來講,Fastjson比較方便,雖然Gson是線程安全的,可以用static變量來聲明一個Gson實例(餓漢模式的單例)然后全局使用,但是還是比Fastjson多了一步。但是Gson這么做的好處是如果序列化(反序列化)的定制比較多,可以在初始化的時候完成復雜的擴展定制,使用的時候依然保持簡單,Fastjson就需要每次都傳遞額外的參數來實現。總體來講各有優化,Gson是線程安全的,大部分場景都是定義全局的靜態單例,用起來跟Fastjson差不多。Gson在這里的選擇傾向于希望對外的接口保持一致和簡單,即無論怎么定制邏輯,Json的序列化和反序列化就那么幾個方法,內部邏輯可以定制,但是使用接口不用改變。
數字的默認類型
對于數字類型,如果沒有明確指定類型,Gson默認都解析成Double類型,而Fastjson會根據數字的不同,解析成Long、Integer或者BigDecimal。我們在生產中用Fastjson就遇到這種問題:由于集合沒有指定泛型類型,反序列化的時候,不同大小的數字被反序列化成了不同的類型,導致業務邏輯出錯。這種未制定類型情況下,感覺Gson的處理更合適一些,既然未指定類型,對外的默認類型始終是Double,接口對外的心智更穩定。
集合類型反序列化
對于列表類型的反序列化,Fastjson提供了parseArray系列方法,這樣很多情況下可以避免使用TypeReference,代碼寫起來更簡單。但是Gson就沒有這種方法,如果需要解析列表,必須使用TypeToken<List<Xxx>>,并沒有為列表設置特殊的方法,這里依然能看到 Gson希望對外的接口保持一致和簡單 ,即便犧牲一點兒方便性。
泛型反序列化
為了解析泛型,Gson和Fastjson都提供了類似的機制(Gson使用TypeToken承載類型,而Fastjson使用TypeReference承載類型),利用子類繼承確定泛型類型父類的方式,獲得類型,區別是Gson的接口只接受Type類型的參數,不接受TypeToken參數,這是因為Type是JDK的自帶類型,這種設計的效果是Gson的接口非常簡單。Fastjson的接口可以支持Type參數,也支持TypeReference參數。
小結
整體上能明顯看出來fastjson更多是長出來的,接口多而全,應該是不斷有人提需求的結果,而Gson是設計出來的,接口的一致性很強,高內聚低耦合,有些時候寧愿犧牲接口的便利性,也要保證接口對外的一致性、簡單和概念完整,從設計上我是崇尚Gson的設計理念的,但實際的開發過程更容易演變成fastjson的模式,在中國程序員的地位真的不夠高。
參考資料
[1]. fastjson源代碼
[2]. Gson源代碼
[3]. https://github.com/google/gson/blob/master/GsonDesignDocument.md
總結
以上是生活随笔為你收集整理的Json反序列化与Java泛型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PXE+Kickstart实现无人值守批
- 下一篇: 将php-fpm添加至service服务