基礎篇:深入解析JAVA泛型和Type型別體系

cscw發表於2020-10-08

1 JAVA的Type型別體系

  • 先了解下java的Type型別體系,Type是所有型別(原生型別-Class、引數化型別-Parameterizedtype、陣列型別-GenericArrayType、型別變數-TypeVariable、基本型別-Class)的共同介面;前兩篇反射和註解講到的Class<T>就是Type的一實現類
  • Type下面又有四個子介面類ParameterizedType、TypeVariable、GenericArrayType、WildcardType
    • List<E>表示泛型,E是TypeVariable型別,List<String>則是ParameterizedType(引數化型別),List<String>裡的String稱為實際引數型別
    • 具體化泛型中的型別時,可以使用 ? extends 或 ? super來表示繼承關係;如List<\? extends Data\>,而裡面的 ? 稱為萬用字元型別
    • GenericArrayType 表示一種元素型別是ParameterizedType(引數化型別)或者TypeVariable(型別變數)的陣列型別,如T[] 或者 List[]
  • 註解是JDK1.5才出現了的,為了表示被註解的型別的,加入AnnotatedElement型別,字面意思就是被註解的元素。JDK1.8又有了AnnotatedType將Type和被註解元素的概念關聯起來。
  • AnnotatedType也有四個子介面,和Type的四個子介面一一對應,如:ParameterizedType型別被註解則被編譯器解析成AnnotatedParameterizedType: @AnTest("list")List<String>list

2 泛型的概念

  • Java 泛型(generics)是JDK1.5中引入的一個新特性,其本質是引數化型別,解決不確定具體物件型別的問題;其所操作的資料型別被指定為一個引數(type parameter)這種引數型別可以用在類、介面和方法的建立中,分別稱為泛型類、泛型介面、泛型方法

泛型: 把型別明確的工作推遲到建立物件或呼叫方法的時候才去明確的特殊的型別

3 泛型類和泛型方法的示例

  • 泛型類的定義
public class MainTest<T> {
    private  T param;
}
// 存在上界Object
class MainTest<T extends Object> { } 
public static void main(String[] args){
        MainTest<String> data = new MainTest<String>(){};
        ParameterizedType genType1 = (ParameterizedType)data.getClass().getGenericSuperclass();
  }
  • 泛型方法的定義
public class MainTest{
    public static void main(String[] args){
        printData("siting");
    }
    static  <T> T printData(T t){
        System.out.println(t);
        return t;
    }
}
  • 介面和抽象類都可以使用泛型

4 型別擦除

  • 建立泛型的例項時,jvm是會把具體型別擦除的;編譯生成的位元組碼中不包含泛型中的型別引數,即ArrayList<String>\ArrayList<Integer>都擦除成了ArrayList,也就是被擦除成"原生型別",這就是泛型擦除
public class MainTest {
    public static void main(String[] args){
        List<String> strArr  = new ArrayList<>();
        List<Integer> intArr  = new ArrayList<>();
        Type strClazz = strArr.getClass();
        Type intClazz = intArr.getClass();
    }
}

  • 檢視編譯後的位元組碼檔案是如何表示的: idea選單 -> view -> show ByteCode
public class MainTest<T> {
    T param;
    public static void main(String[] args){
        MainTest<String> test = new MainTest<>();
        test.setParam("siting");
    }
    public T getParam() {  return param;   }
    public void setParam(T param) {  this.param = param;  }
}
public class com/MainTest {
  ...省略
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 7 L0
    NEW com/MainTest
    DUP
    INVOKESPECIAL com/MainTest.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 8 L1
    ALOAD 1
    LDC "siting"     // 呼叫型別擦除後的setParam(Object)
    INVOKEVIRTUAL com/MainTest.setParam (Ljava/lang/Object;)V
   L2
   ...省略//getParam 的返回值是Object
  public getParam()Ljava/lang/Object;
   L0
    LINENUMBER 10 L0
    ALOAD 0
    GETFIELD com/MainTest.param : Ljava/lang/Object;
    ARETURN
   ...省略//setParam 的入參是Object
  public setParam(Ljava/lang/Object;)V
   L0
    LINENUMBER 11 L0
    ALOAD 0
    ALOAD 1
    PUTFIELD com/MainTest.param : Ljava/lang/Object;
    RETURN
   ...
}
  • 可以看出T(String)都被轉換為Object型別,最初的初始化的String不見了

5 引數化型別ParameterizedType

public interface ParameterizedType extends Type {
    //獲取實際引數,List<String>裡的String; 如果是List<T>則是TypeVariable型別
    Type[] getActualTypeArguments(); 
    // 獲取原始型別List<String> -> List<E>
    Type getRawType();  
    Type getOwnerType();
}
  • 需要注意的點,我們不能直接獲取指定具體引數的泛型的型別,如Class clazz = List<String>.class編譯時不通過的;還有就是直接通過泛型類new建立的物件,其Class並非ParameterizedType型別,而是泛型本身的class,示例如下
public class MainTest<T> {
    public static void main(String[] args){
        MainTest<String> str = new MainTest<String>();
        Class variable = str.getClass();
        Type genType1 = variable.getGenericSuperclass();
    }
}

  • 被具體引數化的泛型才能被編譯器識別為ParameterizedType型別,有三種方式獲取ParameterizedType型別
// 1 子類繼承泛型時,指定具體引數(可以是String等已知型別,也可以是子類的泛型引數)
// 2 獲取在類內部定義的泛型屬性
// 3 區域性程式碼,可以通過泛型的匿名內部子類獲取ParameterizedType型別
public class MainTest<T> {
    List<T> list;
    public static void main(String[] args) throws NoSuchFieldException {
        SubTest<String> str = new SubTest<>();
        // 方式一
        Class variable = str.getClass();
        // 父類是(521)ParameterizedType型別
        ParameterizedType genType = (ParameterizedType)variable.getGenericSuperclass();
        // (521)ParameterizedType型別的原生型別是(479)class com.MainTest
        Type clazz = genType.getRawType();
        //MainTest.class 的原生型別是 (479)class com.MainTest
        Class rawClazz = MainTest.class;

        //方式二,泛型屬性
        Field field = rawClazz.getDeclaredField("list");
        //屬性list 型別是(546)ParameterizedType型別List<T>
        ParameterizedType fieldType = (ParameterizedType)field.getGenericType();

        // 方式三
        MainTest<String> sub3 = new MainTest<String>(){};
        // clazz3是匿名子類
        Class clazz3 =  sub3.getClass();
        //父類是(555)ParameterizedType型別
        ParameterizedType genType3 = (ParameterizedType) clazz3.getGenericSuperclass();
        // (555)ParameterizedType型別的原生型別是(479)class com.MainTest
        Type type3 = genType3.getRawType();
    }
    public static class SubTest<R> extends MainTest<R>{ }
}

6 泛型的繼承

  • 子類可以指定父類的泛型引數,可以是已知類(Integer、String等),也可以用子類自己的泛型引數指定
  • 泛型被繼承時,且指定父類泛型引數,則額外生成的ParameterizedType型別作為子類的父類;如果沒有指定父類泛型引數,則直接繼承原生型別
public class MainTest<T> {
    T param;
    static public class SubTest1 extends MainTest<String>{}
    static public class SubTest2<R> extends MainTest<R>{}
    //SubTest3繼承的時原生型別
    static public class SubTest3 extends MainTest{}
}

7 泛型變數TypeVariable

  • (先臨時定義一個名稱,Test<E>裡的E為泛型引數);泛型變數TypeVariable:泛型的泛型引數就是TypeVariable;當父類使用子類的泛型引數指定自身的泛型引數時;或者泛型屬性定義在泛型類A<T>中,並使用泛型類A<T>的泛型引數T時,其泛型引數都會被編譯器定為泛型變數TypeVariable,而不是被擦除
public class MainTest<T> {
    List<T> param;
    public static void main(String[] args) throws Exception{
        Class clazz =  MainTest.class;
        TypeVariable[] typeVariable = clazz.getTypeParameters();
        // 1
        Field field = clazz.getDeclaredField("param");
        ParameterizedType arrayType = (ParameterizedType)field.getGenericType();
        // interface List<E> 的泛型型別E被T,具體化,因此其被識別為 TypeVariable
        TypeVariable variable1 = (TypeVariable)arrayType.getActualTypeArguments()[0];
        // 2
        ParameterizedType type = (ParameterizedType)SubTest.class.getGenericSuperclass();
        TypeVariable variable2 = (TypeVariable)type.getActualTypeArguments()[0];
    }
    static class SubTest<R> extends MainTest<R>{}
}

8 萬用字元(WildcardType)

無邊界萬用字元:無界萬用字元 ? 可以適配任何引用型別:

  • 當方法引數需要傳入一個泛型時,而且無法確定其型別時。直接使用無具體泛型變數的泛型,容易造成安全隱患;若在方法程式碼裡進行型別轉換,極容易出現ClassCastException錯誤
  • 那泛型變數用Object代替不就行了?但是泛型類+具體引數轉變的ParameterizedType(引數化型別)是不存在繼承關係;即Object是String的父類,但是List<Object>和List<String>的型別是不同的兩個ParameterizedType,不存在繼承關係。於是有了型別萬用字元 ?
public static void print(List list){} 
----->>>
public static void print(List<?> list){} 

  • 無界萬用字元可以匹配任意型別;但是在使用?時,不能給泛型類的變數設定值,因為我們不知道具體型別是什麼;如果強行設定新值,後面的讀容易出現ClassCastException錯誤。因此編譯器限制了萬用字元 ?的泛型只能讀不能寫

上界限定萬用字元 < ? extends E>

  • 想接收一個List集合,它只能運算元字型別的元素【Float、Integer、Double、Byte等數字型別都行】,怎麼做?可以使用List<? extends Number的子類>,表明List裡的元素都是Number的子類
    public static void print(List<? extends Number> list) {
        Number n = new Double("1.0");
        list.add(n);
        Number tmp = list.get(0);
    }

  • 圖片裡可以看出,存在上界萬用字元,因為具體型別不確定,也是隻能讀不能寫的

下界限定萬用字元 < ? super E>

class Parent{ }
class Child extends Parent{ }
public class MainTest<T> {
    T param;
    public static void main(String[] args){
        MainTest<? super Child> parent_m = new MainTest<>();
        parent_m.setParam(new Child());
        Object parent = parent_m.getParam();
    }
    public T getParam() {  return param;  }
    public void setParam(T param) {  this.param = param; }
}

  • 如果定義了萬用字元是誰的父類,則是下界限定萬用字元;此類萬用字元可讀可寫,轉成任意父類都不會出現ClassCastException錯誤。
  • 個人猜想:難道是因為萬用字元上界限定萬用字元的泛型 向下轉型容易出現ClassCastException錯誤,而下界限定萬用字元向上轉型不會出現ClassCastException錯誤,因此java規範限制前者編譯出錯,而後面編譯通過?

9 泛型陣列(GenericArrayType)

public interface GenericArrayType extends Type {
    //獲得這個陣列元素型別,即獲得:A<T>(A<T>[])或T(T[])
    Type getGenericComponentType();
}
  • GenericArrayType,泛型陣列,描述的是ParameterizedType型別以及TypeVariable型別陣列,即形如:Test<T>[][]、T[]等,是GenericArrayType的子介面
public class MainTest<T> {
    T[] param;
    public static void main(String[] args) throws Exception{
        Class clazz =  MainTest.class;
        Field field = clazz.getDeclaredField("param");
        GenericArrayType arrayType = (GenericArrayType)field.getGenericType();
        TypeVariable variable = (TypeVariable) arrayType.getGenericComponentType();
    }
}

歡迎指正文中錯誤

關注公眾號,一起交流

相關文章