基礎篇:深刻解析JAVA泛型

1 JAVA的Type類型體系

  • 先了解下java的Type類型體系(類的類=>類型),Type是全部類型(原生類型-Class、參數化類型-Parameterizedtype、數組類型-GenericArrayType、類型變量-TypeVariable、基本類型-Class)的共同接口;前兩篇反射和註解講到的Class<T>就是Type的一實現類

  • Type下面又有四個子接口類ParameterizedType、TypeVariable、GenericArrayType、WildcardTypejava

    • List<E>表示泛型,E是TypeVariable類型,List<String>則是ParameterizedType(參數化類型),List<String>裏的String稱爲實際參數類型
    • 具體化泛型中的類型時,可使用 ? extends 或 ? super來表示繼承關係;如List<? extends Data>,而裏面的 ? 稱爲通配符類型WildcardType
    • GenericArrayType 表示一種元素類型是ParameterizedType(參數化類型)或者TypeVariable(類型變量)的數組類型,如T[] 或者 List<E>[]
  • 註解是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;
}
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 泛型的繼承

  • 子類能夠指定父類的泛型參數,能夠是已知類(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{}
}

6 泛型變量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>{}
}

7 參數化類型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>{ }
}

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();
    }
}


歡迎指正文中錯誤

關注公衆號,一塊兒交流

相關文章
相關標籤/搜索