Java泛型中的類型擦除以及Type接口

Java 泛型(generics)是 JDK1.5 中引入的一個(gè)新特性,其本質(zhì)是參數(shù)化類型,解決不確定具體對(duì)象類型的問題;其所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù)(type parameter)這種參數(shù)類型可以用在類、接口和方法的創(chuàng)建中,分別稱為泛型類、泛型接口、泛型方法。

但是在 Java 中并不是真正的泛型,實(shí)際上是“偽泛型”

類型擦除(type Erasure)

為了與之前的版本兼容,JDK1.5 中通過類型擦除來增加的泛型功能。Java 泛型只是在編譯器層次上,在編譯后生成的字節(jié)碼中是不包含泛型中類型的信息的。
通過一個(gè)例子來證明類型擦除

public class main {

    public static void main(String[] args) {
        ArrayList<String> sList = new ArrayList<String>();
        ArrayList<Integer> iList = new ArrayList<Integer>();
        System.out.println(sList.getClass() == iList.getClass());
    }
}

上面定義了兩個(gè) ArrayList,一個(gè)是 ArrayList<String>泛型類型的,一個(gè)是 ArrayList<Integer>類型的,但是最后打印的是true,說明兩個(gè)類型相同。
javap -c看一下生成的生成的字節(jié)碼

可以看到在字節(jié)碼中,ArrayList<String>和 ArrayList<Integer>都被編譯成了 ArrayList 類型,可見編譯后發(fā)生了類型擦除。

  1. 既然編譯后發(fā)生了類型擦除,那么虛擬機(jī)解析、反射等場(chǎng)景是怎么獲取到正確的類型的?

在 JDk1.5 中增加泛型的同時(shí),JCP 組織修改了虛擬機(jī)規(guī)范,增加了Signature、LocalVariableTypeTable新屬性。
javap -v查看一下字節(jié)碼,在main方法中包含一段

LocalVariableTypeTable:
 Start  Length  Slot  Name   Signature
   8      31     1 sList   Ljava/util/ArrayList<Ljava/lang/String;>;
   16      23     2 iList   Ljava/util/ArrayList<Ljava/lang/Integer;>;

LocalVariableTypeTable是一個(gè)可選屬性,如果存在泛型,則會(huì)出現(xiàn)這個(gè)屬性。在Signature下包含了泛型的信息。

  1. 接下來,看這段代碼
ArrayList<String> sList = new ArrayList<String>();
sList.add("111");
String s = sList.get(0);

類型擦除之后,當(dāng)調(diào)用sList.get(0)是如何確保返回的值不會(huì)和 String 不匹配呢?
javap -c查看一下字節(jié)碼

public class com.example.demo.test.main {
       // .....省略
  public static void main(java.lang.String[]) throws java.lang.NoSuchFieldException;
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #4                  // String 111
      11: invokevirtual #5                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
      14: pop
      15: aload_1
      16: iconst_0
      17: invokevirtual #6                  // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
      20: checkcast     #7                  // class java/lang/String
      23: astore_2
      24: return
}

#7處有一個(gè)checkcast指令,checkcast用于檢查類型強(qiáng)制轉(zhuǎn)換是否可以進(jìn)行,也就是泛型在獲取值的時(shí)候進(jìn)行了強(qiáng)制類型轉(zhuǎn)換。

  1. 再來看看下面這段代碼

首先定義一個(gè) Java 泛型類

public class GenericClass<T> {

    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

再定義一個(gè)子類繼承它

public class GenericClassTest extends GenericClass<Integer> {

    @Override
    public void setValue(Integer value) {
        super.setValue(value);
    }

    @Override
    public Integer getValue(){
        return super.getValue();
    }
}

GenericClassTest中將GenericClass的泛型定義為Integer類型,并重寫了 get 和 set 方法,因?yàn)榇嬖陬愋筒脸?,父?code>GenericClass的泛型被擦除了。
javap -c 查看一下GenericClass編譯后的字節(jié)碼

可以看到類型擦除后泛型變?yōu)榱?code>Object。那么GenericClass也就變?yōu)榱?/p>

public class GenericClass {

    private Object value;

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }
}

這樣,父類GenericClass中 set 和 get 方法操作的是 Object 對(duì)象,而子類GenericClassTest 操作的是 Integer 對(duì)象,為什么還可以重寫?按照正常的繼承關(guān)系中,這應(yīng)該是重載。
按照重載的方式試一下

可以看到設(shè)置 Object 對(duì)象出現(xiàn)了紅波浪線,不允許這樣設(shè)置,看來確實(shí)是重寫,而不是重載。為什么會(huì)時(shí)重寫,這不是跟 Java 多態(tài)沖突么?繼續(xù)往下研究。
現(xiàn)在用javap -c看一下子類GenericClassTest的字節(jié)碼文件

GenericClassTest中 get 和/set 方法都有兩個(gè),一個(gè)是操作 Object 對(duì)象一個(gè)是操作 Integer 對(duì)象。
操作 Integer 對(duì)象的是GenericClassTest定義的,操作 Object 對(duì)象的是由編譯器生成的。
再用javap -v 查看一下字節(jié)碼更詳細(xì)的信息。

編譯器生成的兩個(gè)操作 Object 對(duì)象的方法中多了兩個(gè)ACC_BRIDGEACC_SYNTHETIC標(biāo)志。
這就是虛擬機(jī)解決類型擦除和多態(tài)沖突問題的方法:使用橋接方法
橋接方法方法是由編譯器生成的,我們?cè)诖a中并不能直接使用,但是可以通過反射拿到橋接方法再使用。

泛型一旦編譯過后,類型就被擦除了,那到了運(yùn)行時(shí),怎么獲取泛型信息?這就要使用 JDK 提供的 Type 類型接口了。

Type 類型

在沒有泛型之前,所有的類型都通過 Class 類進(jìn)行抽象,Class 類的一個(gè)具體對(duì)象就代表了一個(gè)類型。
在 JDK1.5 增加了泛型之后,擴(kuò)充了數(shù)據(jù)類型,將泛型也包含了。
JDK 在原來的基礎(chǔ)上增加了一個(gè)Type接口,它是所有類型的父接口,它的子類有

  • Class類: 原始/基本類型,包括平時(shí)我們所有的類、枚舉、數(shù)組、注解,還有 int、float 等基本類型
  • ParameterizedType接口:參數(shù)化類型,比如 List<String>
  • TypeVariable接口:類型變量,比如 List<T>中的 T 就是參數(shù)化變量
  • GenericArrayType接口: 數(shù)組類型,比如 List<String>[]、T[]
  • WildcardType接口:泛型表達(dá)式類型,比如 List< ? extends Number>

ParameterizedType

參數(shù)化類型,即帶有參數(shù)的類型,也就是帶有<>的類型

public interface ParameterizedType extends Type {
        Type[] getActualTypeArguments();

        Type getRawType();

     Type getOwnerType();
}
  • getActualTypeArguments(): 獲取類型內(nèi)部的參數(shù)化類型 比如 Map<K,V>里面的 K,V 類型。
  • getRawType(): 類的原始類型,比如 Map<K,V>中的 Map 類型。
  • getOwnerType(): 獲取所有者類型(只有內(nèi)部類才有所有者,比如 Map.Entry 他的所有者就是 Map),若不是內(nèi)部類,此處返回 null。

實(shí)例:

public class GenericClass<T> {
    private List<String> list;
    private List<T> tList;

    public static void main(String[] args) {
        Class<GenericClass> genericClassClass = GenericClass.class;
        Field[] declaredFields = genericClassClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            Type genericType = declaredField.getGenericType();
            if (genericType instanceof ParameterizedType) {
                System.out.println("==========" + genericType.getTypeName() + "======ParameterizedType類型=====");
                ParameterizedType parameterizedType = (ParameterizedType) genericType;
                System.out.println("getActualTypeArguments:");
                Type[] actualTypeArguments = (parameterizedType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println("    " + actualTypeArgument);
                }
                Type rawType = (parameterizedType).getRawType();
                System.out.println("getRawType:");
                System.out.println("    " + rawType);

            }
        }
    }
}

輸出

==========java.util.List<java.lang.String>======ParameterizedType類型=====
getActualTypeArguments:
    java.lang.String
getRawType:
    interface java.util.List
==========java.util.List<T>======ParameterizedType類型=====
getActualTypeArguments:
    T
getRawType:
    interface java.util.List

TypeVariable

類型變量,即泛型中的變量,例如:T、K、V 等變量,可以表示任何類;

注意: 與 ParameterizedType 的區(qū)別,TypeVariable 代表著泛型中的變量,而 ParameterizedType 則代表整個(gè)泛型。比如 List<T>中,T 是 TypeVariable 類型,List<T>是 ParameterizedType 類型

public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {

    Type[] getBounds();

    D getGenericDeclaration();

    String getName();
    // JDK8新增的
    AnnotatedType[] getAnnotatedBounds();
}
  • getBounds():類型對(duì)應(yīng)的上限,默認(rèn)為 Object 可以有多個(gè)。比如 List< T extends Number & Serializable>中的 Number 和 Serializable
  • getGenericDeclaration(): 獲取聲明該類型變量實(shí)體,比如 GenericClass< T>中的 GenericClass
  • getName():獲取類型變量在源碼中定義的名稱;

實(shí)例:

public class GenericClass<T extends Number> {
    private T t;

    public static void main(String[] args) {
        Class<GenericClass> genericClassClass = GenericClass.class;
        Field[] declaredFields = genericClassClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            Type genericType = declaredField.getGenericType();
            if (genericType instanceof TypeVariable) {
                System.out.println("==========" + genericType.getTypeName() + "======TypeVariable類型=====");
                TypeVariable typeVariable = (TypeVariable) genericType;
                Type[] bounds = typeVariable.getBounds();
                System.out.println("getBounds:");
                for (Type bound : bounds) {
                    System.out.println("    " + bound);
                }
                System.out.println("getGenericDeclaration:");
                System.out.println("    " + typeVariable.getGenericDeclaration());
                System.out.println("getName:");
                System.out.println("    " + typeVariable.getName());


            }
        }
    }
}

輸出:

==========T======TypeVariable類型=====
getBounds:
    class java.lang.Number
getGenericDeclaration:
    class com.example.demo.test.GenericClass
getName:
    T

GenericArrayType

泛型數(shù)組類型,用來描述 ParameterizedType、TypeVariable 類型的數(shù)組;例如:List<T>[] 、T[]、List<String>[]等。

注意: GenericArrayType 是來描述與泛型相關(guān)的數(shù)組,與 String[]、int[]、float[]這種類型不同。

public interface GenericArrayType extends Type {

    Type getGenericComponentType();
}
  • getGenericComponentType():返回泛型數(shù)組中元素的 Type 類型,比如 List<String>[] 中的 List<String>

實(shí)例:

public class GenericClass<T extends Number> {

    private List<String>[] lists;
    private T[] ts;

    public static void main(String[] args) {
        Class<GenericClass> genericClassClass = GenericClass.class;
        Field[] declaredFields = genericClassClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            Type genericType = declaredField.getGenericType();

            if (genericType instanceof GenericArrayType) {
                GenericArrayType genericArrayType = (GenericArrayType) genericType;
                System.out.println("==========" + genericType.getTypeName() + "======GenericArrayType類型=====");
                Type genericComponentType = genericArrayType.getGenericComponentType();
                System.out.println("getGenericComponentType:");
                System.out.println("    " + genericComponentType);
            }
        }
    }
}

輸出:

==========java.util.List<java.lang.String>[]======GenericArrayType類型=====
getGenericComponentType:
    java.util.List<java.lang.String>
==========T[]======GenericArrayType類型=====
getGenericComponentType:
    T

WildcardType

泛型表達(dá)式(通配符表達(dá)式)。例如:? extend Number、? super Integer。

注意: WildcardType 雖然是 Type 的子接口,但不代表一種類型,,表示的僅僅是類似 ? extends T、? super K 這樣的通配符表達(dá)式。

public interface WildcardType extends Type {

    Type[] getUpperBounds();

    Type[] getLowerBounds();
}
  • getUpperBounds() 獲得泛型表達(dá)式上界(上限) 獲取泛型變量的上邊界(extends)
  • getLowerBounds() 獲得泛型表達(dá)式下界(下限) 獲取泛型變量的下邊界(super)

實(shí)例:

public class GenericClass<T extends Number> {
    private List<? extends Number> numbers;

    private List<? super Integer> integers;

    public static void main(String[] args) {
        Class<GenericClass> genericClassClass = GenericClass.class;
        Field[] declaredFields = genericClassClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            Type genericType = declaredField.getGenericType();
            if (genericType instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) genericType;

                Type[] actualTypeArguments = (parameterizedType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    if(actualTypeArgument instanceof WildcardType){
                        System.out.println("==========" + actualTypeArgument.getTypeName() + "======WildcardType類型=====");
                        WildcardType wildcardType = (WildcardType) actualTypeArgument;
                        System.out.println("getUpperBounds:");
                        Type[] upperBounds = wildcardType.getUpperBounds();
                        for (Type upperBound : upperBounds) {
                            System.out.println("    "+ upperBound);
                        }
                        System.out.println("getLowerBounds:");
                        Type[] lowerBounds = wildcardType.getLowerBounds();
                        for (Type lowerBound : lowerBounds) {
                            System.out.println("    "+ lowerBound);
                        }

                    }
                }
            }

        }
    }
}

輸出:

==========? extends java.lang.Number======WildcardType類型=====
getUpperBounds:
    class java.lang.Number
getLowerBounds:
==========? super java.lang.Integer======WildcardType類型=====
getUpperBounds:
    class java.lang.Object
getLowerBounds:
    class java.lang.Integer

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容