前言
在 Java 開發(fā)中,獲取泛型這種操作雖不是很常用,但有時確實必須的,比如 將Json 字符串反序列化成對象的時候。今天就來介紹這個操作。
場景
假設(shè)我們定義了一個類,內(nèi)部有一個數(shù)據(jù)結(jié)構(gòu),泛型為 T,當我們輸入一個 Json 字符串,想把這個 Json 反序列化成對象,那么此時,我們就需要知道這個泛型的類型。
具體代碼場景如下:
抽象父類(包含泛型):
abstract class Base<T extends Comparable<T>> {
T data;
public Base(String json) {
this.data = JsonUtil.toObject(json, deSerializable());
}
}
我們想在該類中輸入 Json,并將字符串反序列化成對象。比如下面這樣:
/**
* 子類定義了父類
*/
class Son extends Base<DataClass> {
public Son(String json) {
super(json);
}
}
/**
* 數(shù)據(jù)類型繼承Comparable
*/
class DataClass implements Comparable<DataClass> {
@Override
public int compareTo(DataClass o) {
return 0;
}
}
上面的例子中,子類定義了泛型,但獲取泛型類型是在父類。
所以,重點在 deSerializable() 方法的實現(xiàn),我們需要一個 Class<T> 讓 Json 工具能夠正常序列化。
如何實現(xiàn)?
先說結(jié)論:通過 Java 反射包的 ParameterizedType 工具獲得泛型具體類型。
例如:下面的代碼:
public static void main(String[] args) {
String json = JsonUtil.toJson(new DataClass());
Son s = new Son(json);
Type t = s.getClass().getGenericSuperclass();
if (t instanceof ParameterizedType) {
System.out.println(t);
// output: cn.think.in.java.clazz.loader.generics.Base<cn.think.in.java.clazz.loader.generics.DataClass>
for (Type type : ((ParameterizedType) t).getActualTypeArguments()) {
System.out.println(type);
//output: class cn.think.in.java.clazz.loader.generics.DataClass
}
}
}
首先我們將一個對象序列化成 Json 字符串,模擬外部輸入。然后呢?創(chuàng)建一個子類對象,得到這個 Son 的 Class 。
關(guān)鍵地方來了,調(diào)用 getGenericSuperclass 方法,這個方法的作用是:返回表示此 Class 所表示的實體(類、接口、基本類型或 void)的直接超類的 Type。
所以這里會得到一個 ParameterizedTypeImpl 類型的對象。注意:這個類是 Sun 包下的,不是開源的。該類有以下幾個屬性:

Type[] 數(shù)組就是該類(我們這里是父類)的泛型,rawType 是原始類型,即 Base 的 Class 類型。而 OwnerType 返回的則是 Base 類型。
然后呢,判斷這個 t 是不是 ParameterizedType 接口的實現(xiàn)類。如果是,調(diào)用 getActualTypeArguments 方法,返回一個 Type數(shù)組,即上圖的 actualTypeArguments 屬性。
而返回的 Type 數(shù)組就是父類的泛型 Class。因為 Class 實現(xiàn)了 Type 接口。為什么是數(shù)組呢?因為每個類可以有多個泛型。

通過這樣幾行代碼,我們就得到了泛型。當然,這種用法很少。
現(xiàn)在我們知道了如何得到泛型,那么,就將剛剛的場景中的問題解決。
實現(xiàn)反序列化方法:
private Class<T> deSerializable() {
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
System.out.println(parameterizedType.getActualTypeArguments()[0]);
return (Class<T>) parameterizedType.getActualTypeArguments()[0];
}
throw new RuntimeException();
}
獲取到當前類(Son)的泛型 Class,獲取到泛型數(shù)組,返回第一個(因為我們只有一個泛型)泛型類型的 Class<T>。
然后,使用 Json 工具傳入 Json 字符串和 Class<T> 類型并返回實體對象。
這樣就能夠保證編譯不會錯誤,且高度靈活。
這里有一個地方需要注意:Java 的泛型是會在運行期擦除的,但并不總是擦除成 Object ,而是擦除到上限類型。
如果時獲取接口的泛型則是調(diào)用 Class 的 getGenericInterfaces 方法得到接口集合的泛型。
總結(jié)
因為歷史原因,Java 的泛型一直是個痛點,但無法避免,所以使用起來確實有點麻煩。但通過 Class 類的眾多反射功能,我們還是能夠處理泛型的問題。
我們今天使用反射得到了一個類的泛型,并在父類進行處理,成功的將一個字符串反序列化成一個對象。
bye!