關(guān)于Java的TypeReference
我們知道,Java中的泛型是編譯期的,在運(yùn)行時其會被擦除掉,比如我們編寫代碼List<Integer> lst = new ArrayList<>();
,從運(yùn)行時看來將會是List lst = new ArrayList();
,只留下了原始類型(raw type)。
但我們有時候確實(shí)需要在運(yùn)行時獲取一定的泛型信息??紤]這樣的情況:在一個servlet應(yīng)用里(為什么是servlet?因?yàn)閟pring mvc遇不到這個問題),我們要求前端使用JSON來發(fā)送請求,并規(guī)定了請求的格式——
為此,對應(yīng)的POJO為——
在servlet中,我們需要將請求體字符串轉(zhuǎn)換為特定的RequestDto<T>
。比如某個接口要求前端發(fā)送RequestDto<List<Integer>>
。我們在servlet中可能得這么寫——
但這個通不過編譯——所謂的RequestDto<List<Integer>>.class
是不存在的,因?yàn)樵谶\(yùn)行時不存在泛型類型,我們只能得到RequestDto.class
,所以只能這么寫——
雖然有個惱火的警告,但至少能編譯了。我們整個demo試試——
成了!我們再試試錯誤的輸入?
拋異常了!這符合預(yù)期,但是卻是在req.getData()
時拋的cast異常,而非json轉(zhuǎn)換時拋出異常。
這是腫么回事呢?從運(yùn)行時看來,我們是在試圖將字符串{"type":"search", "data": "hello world!"}
轉(zhuǎn)換成類型RequestDto,即——
這河里嗎?可太合理了,既然是Object
,那是任何類型都是可以的了。但這顯然是不符合我們的需要的——如果類型的錯誤必須要在我們使用的時候才能暴露出來,那這和動態(tài)類型語言何異?
問題就出在Java的泛型擦除機(jī)制。我們有什么手段來規(guī)避它嗎?庫函數(shù)的設(shè)計(jì)者告訴我們,有!
Java的泛型擦除機(jī)制實(shí)際上至少在兩個地方?jīng)]有擦掉——方法的參數(shù)和返回值;繼承泛型類的類。
獲取其的demo如下——
前者顯然為Spring mvc所利用——控制器的接口能夠正確處理泛型類,而后者則是所謂的TypeReference所利用的——通過繼承的方式來保存泛型信息。我們可以通過匿名實(shí)現(xiàn)類來在行內(nèi)(inline)直接拿到該信息。
這樣,我們實(shí)際上就能夠間接地表示RequestDto<Integer>.class
了。對上面的json反序列化的代碼,我們可以使用TypeReference的匿名實(shí)現(xiàn)類而非class來保留泛型信息——
我們?nèi)耘f會得到一個異常,但這個異常是符合預(yù)期的,容易理解的,是在進(jìn)行反序列化中拋出的!