Android、Java泛型掃盲

不怕天黑發表於2019-04-22

首先我們定義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()); //編譯不通過
複製程式碼

知識點:

  1. 上邊界符 ? extends 只是限定了賦值給它的例項型別(這裡為賦值給listC的例項型別),且邊界包括自身。
  2. 上邊界符 ? 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()); //編譯通過
複製程式碼

知識點

  1. 下邊界符 ?super,跟上邊界符一樣,只是限定了賦值給它的例項型別,也包括邊界自身
  2. 下邊界符 ?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類進行泛型傳遞。

相關文章