首先我們定義A、B、C、D四個類,他們的關係如下
class A {}
class B extends A {}
class C extends B {}
class D extends C {}
複製程式碼
不指明泛型型別
//以下程式碼均編譯通過
List list = new ArrayList();
//不指明泛型型別,泛型預設為Object型別,故能往裡面新增任意例項物件
list.add(new A());
list.add(new B());
list.add(new C());
//取出則預設為Object型別
Object o = list.get(0);
複製程式碼
這個好理解,因為所有的類都繼承與Object,故能往list裡面新增任意例項物件
無邊界萬用字元 ?
首先我們要明白一個概念,萬用字元?
意義就是它是一個未知的符號,可以是代表任意的類。
//我們發現,這樣寫編譯不通過,原因很簡單,泛型不匹配,雖然B繼承A
List<A> listA = new ArrayList<B>();
//以下5行程式碼均編譯通過
List<?> list;
list = new ArrayList<A>();
list = new ArrayList<B>();
list = new ArrayList<C>();
list = new ArrayList<D>();
Object o = list.get(0); //編譯通過
list.add(new A()); //編譯不通過
list.add(new B()); //編譯不通過
list.add(new C()); //編譯不通過
list.add(new D()); //編譯不通過
複製程式碼
知識點
- 無邊界萬用字元
?
能取不能存。這個好理解,因為編譯器不知道?
具體是啥型別,故不能存;但是任意型別都繼承於Object,故能取,但取出預設為Object物件。
上邊界符 ?extends
繼續上程式碼
List<? extends C> listC;
listC = new ArrayList<A>(); //編譯不通過
listC = new ArrayList<B>(); //編譯不通過
listC = new ArrayList<C>(); //編譯通過
listC = new ArrayList<D>(); //編譯通過
C c = listC.get(0); //編譯通過
listC.add(new C()); //編譯不通過
listC.add(new D()); //編譯不通過
複製程式碼
知識點
:
- 上邊界符
? extends
只是限定了賦值給它的例項型別(這裡為賦值給listC的例項型別),且邊界包括自身。 - 上邊界符
? extends
跟?
一樣能取不能存,道理是一樣的,雖然限定了上邊界,但編譯器依然不知道?
是啥型別,故不能存;但是限定了上邊界,故取出來的物件型別預設為上邊界的型別
下邊界符 ?super
List<? super B> listB;
listB = new ArrayList<A>(); //編譯通過
listB = new ArrayList<B>(); //編譯通過
listB = new ArrayList<C>(); //編譯不通過
listB = new ArrayList<D>(); //編譯不通過
Object o = listB.get(0); //編譯通過
listB.add(new A()); //編譯不通過
listB.add(new B()); //編譯通過
listB.add(new C()); //編譯通過
listB.add(new D()); //編譯通過
複製程式碼
知識點
- 下邊界符
?super
,跟上邊界符一樣,只是限定了賦值給它的例項型別,也包括邊界自身 - 下邊界符
?super
能存能取,因為設定了下邊界,故我們能存下邊界以下的型別,當然也包括邊界自身;然而取得時候編譯器依然不知道?
具體是什麼型別,故取出預設為Object型別。
型別擦除
首先我們要明白一點:Java 的泛型在編譯期有效,在執行期會被刪除 我們來看一段程式碼
//這兩個方法寫在同一個類裡
public void list(List<A> listA) {}
public void list(List<B> listB) {}
複製程式碼
上面的程式碼會有問題嗎?顯然是有的,編譯器報錯,提示如下資訊:
list(List<A>) clashed with list(List<B>) ; both methods have same erasure
翻譯過來就是,在型別擦除後,兩個方法具有相同的簽名,我們來看看型別擦除後是什麼樣子
public void list(List listA) {}
public void list(List listB) {}
複製程式碼
可以看出,兩個方法簽名完全一致,故編譯不通過。 明白了型別擦除,我們還需要明白一個概念
- 泛型類並沒有自己獨有的Class類物件
比如並不存在List<A>.class或是List<B>.class,而只有List.class 接下來這個案例就好理解了
List<A> listA = new ArrayList<A>();
List<B> listB = new ArrayList<B>();
System.out.println(listA.getClass() == listB.getClass()); //輸出true
複製程式碼
泛型傳遞
現實開發中,我們經常會用到泛型傳遞,例如我們經常需要對Http請求返回的結果做反序列化操作
public static <T> T fromJson(String result, Class<T> type) {
try {
return new Gson().fromJson(result, type);
} catch (Exception ignore) {
return null;
}
}
複製程式碼
此時我們傳進去是什麼型別,就會返回自動該型別的物件
String result="xxx";
A a = fromJson(result, A.class);
B b = fromJson(result, B.class);
C c = fromJson(result, C.class);
D d = fromJson(result, D.class);
Integer integer = fromJson(result, Integer.class);
String str = fromJson(result, String.class);
Boolean boo = fromJson(result, Boolean.class);
複製程式碼
那如果我們想返回一個集合呢,如List<A>
,下面這樣明顯是不對的。
//編譯報錯,前面型別擦除時,我們講過,不存List<A>.class這種型別
ArrayList<A> list = fromJson(result, ArrayList<A>.class);
複製程式碼
那我們該怎麼做呢?首先,我們對fromJson
改造一下,如下:
//type為一個陣列型別
public static <T> List<T> fromJson(String result, Class<T[]> type) {
try {
T[] arr = new Gson().fromJson(result, type);//首先拿到陣列
return Arrays.asList(arr); //陣列轉集合
} catch (Exception ignore) {
return null;
}
}
複製程式碼
這個時候我們就可以這麼做了
String result="xxx";
List<A> listA = fromJson(result, A[].class);
List<B> listB = fromJson(result, B[].class);
List<C> listC = fromJson(result, C[].class);
List<D> listD = fromJson(result, D[].class);
List<Integer> listInt = fromJson(result, Integer[].class);
List<String> listStr = fromJson(result, String[].class);
List<Boolean> listBoo = fromJson(result, Boolean[].class);
複製程式碼
ok,我在再來,相信大多數Http介面返回的資料格式是這樣的:
public class Response<T> {
private T data;
private int code;
private String msg;
//省略get/set方法
}
複製程式碼
那這種我們又該如何傳遞呢?顯然用前面的兩個fromJson
方法都行不通,我們再來改造一下,如下:
//這裡我們直接傳遞一個Type型別
public static <T> T fromJson(String result, Type type) {
try {
return new Gson().fromJson(result, type);
} catch (Exception ignore) {
return null;
}
}
複製程式碼
這個Type是什麼鬼?點進去看看
public interface Type {
default String getTypeName() {
return toString();
}
}
複製程式碼
哦,原來就是一個介面,並且只有一個方法,我們再來看看它的實現類
發現有5個實現類,其中4個是介面,另外一個是Class類,我們再來看看Class類的宣告public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
//省略內部程式碼
}
複製程式碼
現在有沒有明白點,現在我們重點來關注下Type
介面的其中一個實現介面ParameterizedType
,我們來看下它的內部程式碼,裡面就只有3個方法
public interface ParameterizedType extends Type {
/**
* 例如:
* List<String> list; 則返回 {String.class}
* Map<String,Long> map; 則返回 {String.class,Long.class}
* Map.Entry<String,Long> entry; 則返回 {String.class,Long.class}
*
* @return 以陣列的形式返回所有的泛型型別
*/
Type[] getActualTypeArguments();
/**
* 例如:
* List<String> list; 則返回 List.class
* Map<String,Long> map; 則返回 Map.class
* Map.Entry<String,Long> entry; 則返回 Entry.class
*
* @return 返回泛型類的真實型別
*/
Type getRawType();
/**
* 例如:
* List<String> list; 則返回 null
* Map<String,Long> map; 則返回 null
* Map.Entry<String,Long> entry; 則返回 Map.class
*
* @return 返回泛型類持有者的型別,這裡可以簡單理解為返回外部類的型別,如果沒有外部類,則返回null
*/
Type getOwnerType();
}
複製程式碼
顧名思義,ParameterizedType
代表一個引數化型別。
這個時候我們來自定義一個類,並實現ParameterizedType介面,如下:
public class ParameterizedTypeImpl implements ParameterizedType {
private Type rawType;//真實型別
private Type actualType;//泛型型別
public ParameterizedTypeImpl(Type rawType,Type actualType) {
this.rawType = rawType;
this.actualType = actualType;
}
public Type[] getActualTypeArguments() {
return new Type[]{actualType};
}
public Type getRawType() {
return rawType;
}
public Type getOwnerType() {
return null;
}
}
複製程式碼
我們再次貼出fromJson
方法
//這裡我們直接傳遞一個Type型別
public static <T> T fromJson(String result, Type type) {
try {
return new Gson().fromJson(result, type);
} catch (Exception ignore) {
return null;
}
}
複製程式碼
此時我們想得到Response<T>
物件,就可以這樣寫
Response<A> responseA = fromJson(result, new ParameterizedTypeImpl(Response.class, A.class));
Response<B> responseB = fromJson(result, new ParameterizedTypeImpl(Response.class, B.class));
Response<C> responseC = fromJson(result, new ParameterizedTypeImpl(Response.class, C.class));
複製程式碼
想得到List<T>
物件,也可以通過ParameterizedTypeImpl
得到,如下:
List<A> listA = fromJson(result, new ParameterizedTypeImpl(List.class, A.class));
List<B> listB = fromJson(result, new ParameterizedTypeImpl(List.class, B.class));
List<C> listC = fromJson(result, new ParameterizedTypeImpl(List.class, C.class));
複製程式碼
然而,如果我們想得到Response<List<T>>
物件,又該如何得到呢?
ParameterizedTypeImpl
一樣能夠實現,如下:
//第一步,建立List<T>物件對應的Type型別
Type listAType = new ParameterizedTypeImpl(List.class, A.class);
Type listBType = new ParameterizedTypeImpl(List.class, B.class);
Type listCType = new ParameterizedTypeImpl(List.class, C.class);
//第二步,建立Response<List<T>>物件對應的Type型別
Type responseListAType = new ParameterizedTypeImpl(Response.class, listAType);
Type responseListBType = new ParameterizedTypeImpl(Response.class, listBType);
Type responseListCType = new ParameterizedTypeImpl(Response.class, listCType);
//第三步,通過Type物件,獲取對應的Response<List<T>>物件
Response<List<A>> responseListA = fromJson(result, responseListAType);
Response<List<B>> responseListB = fromJson(result, responseListBType);
Response<List<C>> responseListC = fromJson(result, responseListCType);
複製程式碼
然後,能不能再簡單一點呢?可以,我們對ParameterizedTypeImpl
改造一下
/**
* User: ljx
* Date: 2018/10/23
* Time: 09:36
*/
public class ParameterizedTypeImpl implements ParameterizedType {
private final Type rawType;
private final Type ownerType;
private final Type[] actualTypeArguments;
//適用於單個泛型引數的類
public ParameterizedTypeImpl(Type rawType, Type actualType) {
this(null, rawType, actualType);
}
//適用於多個泛型引數的類
public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... actualTypeArguments) {
this.rawType = rawType;
this.ownerType = ownerType;
this.actualTypeArguments = actualTypeArguments;
}
/**
* 本方法僅使用於單個泛型引數的類
* 根據types陣列,確定具體的泛型型別
* List<List<String>> 對應 get(List.class, List.class, String.class)
*
* @param types Type陣列
* @return ParameterizedTypeImpl
*/
public static ParameterizedTypeImpl get(@NonNull Type rawType, @NonNull Type... types) {
final int length = types.length;
if (length > 1) {
Type parameterizedType = new ParameterizedTypeImpl(types[length - 2], types[length - 1]);
Type[] newTypes = Arrays.copyOf(types, length - 1);
newTypes[newTypes.length - 1] = parameterizedType;
return get(rawType, newTypes);
}
return new ParameterizedTypeImpl(rawType, types[0]);
}
//適用於多個泛型引數的類
public static ParameterizedTypeImpl getParameterized(@NonNull Type rawType, @NonNull Type... actualTypeArguments) {
return new ParameterizedTypeImpl(null, rawType, actualTypeArguments);
}
public final Type[] getActualTypeArguments() {
return actualTypeArguments;
}
public final Type getOwnerType() {
return ownerType;
}
public final Type getRawType() {
return rawType;
}
}
複製程式碼
此時,我們就可以這樣寫
//第一步,直接建立Response<List<T>>物件對應的Type型別
Type responseListAType = ParameterizedTypeImpl.get(Response.class, List.class, A.class);
Type responseListBType = ParameterizedTypeImpl.get(Response.class, List.class, B.class)
Type responseListCType = ParameterizedTypeImpl.get(Response.class, List.class, C.class)
//第二步,通過Type物件,獲取對應的Response<List<T>>物件
Response<List<A>> responseListA = fromJson(result, responseListAType);
Response<List<B>> responseListB = fromJson(result, responseListBType);
Response<List<C>> responseListC = fromJson(result, responseListCType);
複製程式碼
現實開發中,我們還可能遇到這樣的資料結構
{
"code": 0,
"msg": "",
"data": {
"totalPage": 0,
"list": []
}
}
複製程式碼
此時,Response<T>
裡面的泛型傳List肯定是不能正常解析的,我們需要再定一個類
public class PageList<T>{
private int totalPage;
private List<T> list;
//省略get/set方法
}
複製程式碼
此時就可以這樣解析資料
//第一步,直接建立Response<PageList<T>>物件對應的Type型別
Type responsePageListAType = ParameterizedTypeImpl.get(Response.class, PageList.class, A.class);
Type responsePageListBType = ParameterizedTypeImpl.get(Response.class, PageList.class, B.class)
Type responsePageListCType = ParameterizedTypeImpl.get(Response.class, PageList.class, C.class)
//第二步,通過Type物件,獲取對應的Response<PageList<T>>物件
Response<PageList<A>> responsePageListA = fromJson(result, responsePageListAType);
Response<PageList<B>> responsePageListB = fromJson(result, responsePageListBType);
Response<PageList<C>> responsePageListC = fromJson(result, responsePageListCType);
複製程式碼
注:ParameterizedTypeImpl get(Type... types)
僅僅適用於單個泛型引數的時候,如Map等,有兩個泛型引數以上的不要用此方法獲取Type型別。如果需要獲取Map等兩個泛型引數以上的Type型別。可呼叫getParameterized(@NonNull Type rawType, @NonNull Type... actualTypeArguments)
構造方法獲取,如:
//獲取 Map<String,String> 對應的Type型別
Type mapType = ParameterizedTypeImpl.getParameterized(Map.class, String.classs, String.class)
//獲取 Map<A,B> 對應的Type型別
Type mapType = ParameterizedTypeImpl.getParameterized(Map.class, A.classs, B.class)
複製程式碼
到這,泛型相關知識點講解完畢,如有疑問,請留言。
感興趣的同學,可以檢視我的另一片文章RxHttp 一條鏈傳送請求,新一代Http請求神器(一))裡面就用到了ParameterizedTypeImpl
類進行泛型傳遞。