1. 程式人生 > >Java中的Type詳解

Java中的Type詳解

轉載自逆水行舟的部落格Java中的Type詳解

本文主要介紹java中Type介面的來歷以及相關的幾個介面。
通過這邊文章,我們可以瞭解到與範型相關的幾個介面, 對範型的分類有個瞭解;
還可以瞭解到Type介面與Class類的關係, 以及Type出現的原因.

反射相關介面

在這裡插入圖片描述

下面就把Type的來龍去脈徹底弄清楚

Type

Type是所有型別的父介面, 如原始型別(raw types,對應Class)、 引數化型別(parameterized types, 對應ParameterizedType)、 陣列型別(array types,對應GenericArrayType)、 型別變數(type variables, 對應TypeVariable)和基本(原生)型別(primitive types, 對應Class), 子介面有ParameterizedType, TypeVariable, GenericArrayType, WildcardType, 實現類有Class

ParameterizedType

具體的範型型別, 如Map<String, String>
有如下方法:

  1. Type getRawType(): 返回承載該泛型資訊的物件, 如上面那個Map<String, String>承載範型資訊的物件是Map
  2. Type[] getActualTypeArguments(): 返回實際泛型型別列表, 如上面那個Map<String, String>實際範型列表中有兩個元素, 都是String
  3. Type getOwnerType(): 返回是誰的member.(上面那兩個最常用)
public class TestType {
    Map<String, String> map;
    public static void main(String[] args) throws Exception {
        Field f = TestType.class.getDeclaredField("map");
        System.out.println(f.getGenericType());                               // java.util.Map<java.lang.String, java.lang.String>
        System.out.println(f.getGenericType() instanceof ParameterizedType);  // true
        ParameterizedType pType = (ParameterizedType) f.getGenericType();
        System.out.println(pType.getRawType());                               // interface java.util.Map
        for (Type type : pType.getActualTypeArguments()) {
            System.out.println(type);                                         // 列印兩遍: class java.lang.String
        }
        System.out.println(pType.getOwnerType());                             // null
    }
}

TypeVariable

型別變數, 範型資訊在編譯時會被轉換為一個特定的型別, 而TypeVariable就是用來反映在JVM編譯該泛型前的資訊.
它的宣告是這樣的: public interface TypeVariable extends Type
也就是說它跟GenericDeclaration有一定的聯絡, 我是這麼理解的:
TypeVariable是指在GenericDeclaration中宣告的、這些東西中的那個變數T、C; 它有如下方法:

  1. Type[] getBounds(): 獲取型別變數的上邊界, 若未明確宣告上邊界則預設為Object
  2. D getGenericDeclaration(): 獲取宣告該型別變數實體
  3. String getName(): 獲取在原始碼中定義時的名字

注意:
型別變數在定義的時候只能使用extends進行(多)邊界限定, 不能用super;
為什麼邊界是一個數組? 因為型別變數可以通過&進行多個上邊界限定,因此上邊界有多個

public class TestType <K extends Comparable & Serializable, V> {
    K key;
    V value;
    public static void main(String[] args) throws Exception {
        // 獲取欄位的型別
        Field fk = TestType.class.getDeclaredField("key");
        Field fv = TestType.class.getDeclaredField("value");
        Assert.that(fk.getGenericType() instanceof TypeVariable, "必須為TypeVariable型別");
        Assert.that(fv.getGenericType() instanceof TypeVariable, "必須為TypeVariable型別");
        TypeVariable keyType = (TypeVariable)fk.getGenericType();
        TypeVariable valueType = (TypeVariable)fv.getGenericType();
        // getName 方法
        System.out.println(keyType.getName());                 // K
        System.out.println(valueType.getName());               // V
        // getGenericDeclaration 方法
        System.out.println(keyType.getGenericDeclaration());   // class com.test.TestType
        System.out.println(valueType.getGenericDeclaration()); // class com.test.TestType
        // getBounds 方法
        System.out.println("K 的上界:");                        // 有兩個
        for (Type type : keyType.getBounds()) {                // interface java.lang.Comparable
            System.out.println(type);                          // interface java.io.Serializable
        }
        System.out.println("V 的上界:");                        // 沒明確宣告上界的, 預設上界是 Object
        for (Type type : valueType.getBounds()) {              // class java.lang.Object
            System.out.println(type);
        }
    }
}

GenericArrayType

範型陣列,組成陣列的元素中有範型則實現了該介面; 它的組成元素是ParameterizedType或TypeVariable型別,它只有一個方法:

Type getGenericComponentType(): 返回陣列的組成物件, 即被JVM編譯後實際的物件
public class TestType <T> {
    public static void main(String[] args) throws Exception {
        Method method = Test.class.getDeclaredMethods()[0];
        // public void com.test.Test.show(java.util.List[],java.lang.Object[],java.util.List,java.lang.String[],int[])
        System.out.println(method);
        Type[] types = method.getGenericParameterTypes();  // 這是 Method 中的方法
        for (Type type : types) {
            System.out.println(type instanceof GenericArrayType);
        }
    }
}

class Test<T> {
    public void show(List<String>[] pTypeArray, T[] vTypeArray, List<String> list, String[] strings, int[] ints) {
    }
}

第一個引數List[]的組成元素List是ParameterizedType型別, 列印結果為true
第二個引數T[]的組成元素T是TypeVariable型別, 列印結果為true
第三個引數List不是陣列, 列印結果為false
第四個引數String[]的組成元素String是普通物件, 沒有範型, 列印結果為false
第五個引數int[] pTypeArray的組成元素int是原生型別, 也沒有範型, 列印結果為false

WildcardType

該介面表示萬用字元泛型, 比如? extends Number? super Integer它有如下方法:

  1. Type[] getUpperBounds(): 獲取範型變數的上界
  2. Type[] getLowerBounds(): 獲取範型變數的下界

注意:
現階段萬用字元只接受一個上邊界或下邊界, 返回陣列是為了以後的擴充套件, 實際上現在返回的陣列的大小是1

public class TestType {
    private List<? extends Number> a;  // // a沒有下界, 取下界會丟擲ArrayIndexOutOfBoundsException
    private List<? super String> b;
    public static void main(String[] args) throws Exception {
        Field fieldA = TestType.class.getDeclaredField("a");
        Field fieldB = TestType.class.getDeclaredField("b");
        // 先拿到範型型別
        Assert.that(fieldA.getGenericType() instanceof ParameterizedType, "");
        Assert.that(fieldB.getGenericType() instanceof ParameterizedType, "");
        ParameterizedType pTypeA = (ParameterizedType) fieldA.getGenericType();
        ParameterizedType pTypeB = (ParameterizedType) fieldB.getGenericType();
        // 再從範型裡拿到萬用字元型別
        Assert.that(pTypeA.getActualTypeArguments()[0] instanceof WildcardType, "");
        Assert.that(pTypeB.getActualTypeArguments()[0] instanceof WildcardType, "");
        WildcardType wTypeA = (WildcardType) pTypeA.getActualTypeArguments()[0];
        WildcardType wTypeB = (WildcardType) pTypeB.getActualTypeArguments()[0];
        // 方法測試
        System.out.println(wTypeA.getUpperBounds()[0]);   // class java.lang.Number
        System.out.println(wTypeB.getLowerBounds()[0]);   // class java.lang.String
        // 看看萬用字元型別到底是什麼, 列印結果為: ? extends java.lang.Number
        System.out.println(wTypeA);
    }
}

再寫幾個邊界的例子:

List<? extends Number>, 上界為class java.lang.Number, 屬於Class型別
List<? extends List>, 上界為java.util.List, 屬於ParameterizedType型別
List<? extends List>, 上界為java.util.List<java.lang.String>, 屬於ParameterizedType型別
List<? extends T>, 上界為T, 屬於TypeVariable型別
List<? extends T[]>, 上界為T[], 屬於GenericArrayType型別
它們最終統一成Type作為陣列的元素型別

Type及其子介面的來歷

泛型出現之前的型別
沒有泛型的時候,只有原始型別。此時,所有的原始型別都通過位元組碼檔案類Class類進行抽象。Class類的一個具體物件就代表一個指定的原始型別。

泛型出現之後的型別
泛型出現之後,擴充了資料型別。從只有原始型別擴充了引數化型別、型別變數型別、限定符型別 、泛型陣列型別。

與泛型有關的型別不能和原始型別統一到Class的原因

  1. 產生泛型擦除的原因
    原始型別和新產生的型別都應該統一成各自的位元組碼檔案型別物件。但是由於泛型不是最初Java中的成分。如果真的加入了泛型,涉及到JVM指令集的修改,這是非常致命的。

  2. Java中如何引入泛型
    為了使用泛型又不真正引入泛型,Java採用泛型擦除機制來引入泛型。Java中的泛型僅僅是給編譯器javac使用的,確保資料的安全性和免去強制型別轉換的麻煩。但是,一旦編譯完成,所有的和泛型有關的型別全部擦除。

  3. Class不能表達與泛型有關的型別
    因此,與泛型有關的引數化型別、型別變數型別、限定符型別 、泛型陣列型別這些型別編譯後全部被打回原形,在位元組碼檔案中全部都是泛型被擦除後的原始型別,並不存在和自身型別對應的位元組碼檔案。所以和泛型相關的新擴充進來的型別不能被統一到Class類中。

  4. 與泛型有關的型別在Java中的表示
    為了通過反射操作這些型別以迎合實際開發的需要,Java就新增了ParameterizedType, TypeVariable, GenericArrayType, WildcardType幾種型別來代表不能被歸一到Class類中的型別但是又和原始型別齊名的型別。

引入Type的原因
為了程式的擴充套件性,最終引入了Type介面作為Class和ParameterizedType, TypeVariable, GenericArrayType, WildcardType這幾種型別的總的父介面。這樣可以用Type型別的引數來接受以上五種子類的實參或者返回值型別就是Type型別的引數。統一了與泛型有關的型別和原始型別Class

Type介面中沒有方法的原因
從上面看到,Type的出現僅僅起到了通過多型來達到程式擴充套件性提高的作用,沒有其他的作用。因此Type介面的原始碼中沒有任何方法。