Java泛型中的類(lèi)型擦除以及Type接口

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

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

類(lèi)型擦除(type Erasure)

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

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>泛型類(lèi)型的,一個(gè)是 ArrayList<Integer>類(lèi)型的稍坯,但是最后打印的是true,說(shuō)明兩個(gè)類(lèi)型相同搓劫。
javap -c看一下生成的生成的字節(jié)碼

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

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

在 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. 接下來(lái),看這段代碼
ArrayList<String> sList = new ArrayList<String>();
sList.add("111");
String s = sList.get(0);

類(lèi)型擦除之后被辑,當(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用于檢查類(lèi)型強(qiáng)制轉(zhuǎn)換是否可以進(jìn)行盼理,也就是泛型在獲取值的時(shí)候進(jìn)行了強(qiáng)制類(lèi)型轉(zhuǎn)換谈山。

  1. 再來(lái)看看下面這段代碼

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

public class GenericClass<T> {

    private T value;

    public T getValue() {
        return value;
    }

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

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

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類(lèi)型,并重寫(xiě)了 get 和 set 方法宏怔,因?yàn)榇嬖陬?lèi)型擦除奏路,父類(lèi)GenericClass的泛型被擦除了。
javap -c 查看一下GenericClass編譯后的字節(jié)碼

可以看到類(lèi)型擦除后泛型變?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;
    }
}

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

可以看到設(shè)置 Object 對(duì)象出現(xiàn)了紅波浪線(xiàn)威兜,不允許這樣設(shè)置销斟,看來(lái)確實(shí)是重寫(xiě),而不是重載椒舵。為什么會(huì)時(shí)重寫(xiě)蚂踊,這不是跟 Java 多態(tài)沖突么?繼續(xù)往下研究笔宿。
現(xiàn)在用javap -c看一下子類(lèi)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_BRIDGE炬灭、ACC_SYNTHETIC標(biāo)志醋粟。
這就是虛擬機(jī)解決類(lèi)型擦除和多態(tài)沖突問(wèn)題的方法:使用橋接方法
橋接方法方法是由編譯器生成的重归,我們?cè)诖a中并不能直接使用米愿,但是可以通過(guò)反射拿到橋接方法再使用。

泛型一旦編譯過(guò)后鼻吮,類(lèi)型就被擦除了育苟,那到了運(yùn)行時(shí),怎么獲取泛型信息椎木?這就要使用 JDK 提供的 Type 類(lèi)型接口了违柏。

Type 類(lèi)型

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

  • Class類(lèi): 原始/基本類(lèi)型讼积,包括平時(shí)我們所有的類(lèi)、枚舉脚仔、數(shù)組勤众、注解,還有 int鲤脏、float 等基本類(lèi)型
  • ParameterizedType接口:參數(shù)化類(lèi)型们颜,比如 List<String>
  • TypeVariable接口:類(lèi)型變量吕朵,比如 List<T>中的 T 就是參數(shù)化變量
  • GenericArrayType接口: 數(shù)組類(lèi)型,比如 List<String>[]窥突、T[]
  • WildcardType接口:泛型表達(dá)式類(lèi)型努溃,比如 List< ? extends Number>

ParameterizedType

參數(shù)化類(lèi)型,即帶有參數(shù)的類(lèi)型阻问,也就是帶有<>的類(lèi)型

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

        Type getRawType();

     Type getOwnerType();
}
  • getActualTypeArguments(): 獲取類(lèi)型內(nèi)部的參數(shù)化類(lèi)型 比如 Map<K,V>里面的 K梧税,V 類(lèi)型。
  • getRawType(): 類(lèi)的原始類(lèi)型称近,比如 Map<K,V>中的 Map 類(lèi)型第队。
  • getOwnerType(): 獲取所有者類(lèi)型(只有內(nèi)部類(lèi)才有所有者,比如 Map.Entry 他的所有者就是 Map)刨秆,若不是內(nèi)部類(lè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類(lèi)型=====");
                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類(lèi)型=====
getActualTypeArguments:
    java.lang.String
getRawType:
    interface java.util.List
==========java.util.List<T>======ParameterizedType類(lèi)型=====
getActualTypeArguments:
    T
getRawType:
    interface java.util.List

TypeVariable

類(lèi)型變量衡未,即泛型中的變量晾蜘,例如:T、K眠屎、V 等變量,可以表示任何類(lèi)肆饶;

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

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

    Type[] getBounds();

    D getGenericDeclaration();

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

實(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類(lèi)型=====");
                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類(lèi)型=====
getBounds:
    class java.lang.Number
getGenericDeclaration:
    class com.example.demo.test.GenericClass
getName:
    T

GenericArrayType

泛型數(shù)組類(lèi)型洽胶,用來(lái)描述 ParameterizedType、TypeVariable 類(lèi)型的數(shù)組裆馒;例如:List<T>[] 姊氓、T[]、List<String>[]等喷好。

注意: GenericArrayType 是來(lái)描述與泛型相關(guān)的數(shù)組翔横,與 String[]、int[]梗搅、float[]這種類(lèi)型不同禾唁。

public interface GenericArrayType extends Type {

    Type getGenericComponentType();
}
  • getGenericComponentType():返回泛型數(shù)組中元素的 Type 類(lèi)型效览,比如 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類(lèi)型=====");
                Type genericComponentType = genericArrayType.getGenericComponentType();
                System.out.println("getGenericComponentType:");
                System.out.println("    " + genericComponentType);
            }
        }
    }
}

輸出:

==========java.util.List<java.lang.String>[]======GenericArrayType類(lèi)型=====
getGenericComponentType:
    java.util.List<java.lang.String>
==========T[]======GenericArrayType類(lèi)型=====
getGenericComponentType:
    T

WildcardType

泛型表達(dá)式(通配符表達(dá)式)。例如:荡短? extend Number丐枉、? super Integer肢预。

注意: WildcardType 雖然是 Type 的子接口矛洞,但不代表一種類(lèi)型,烫映,表示的僅僅是類(lèi)似 ? 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類(lèi)型=====");
                        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類(lèi)型=====
getUpperBounds:
    class java.lang.Number
getLowerBounds:
==========? super java.lang.Integer======WildcardType類(lèi)型=====
getUpperBounds:
    class java.lang.Object
getLowerBounds:
    class java.lang.Integer

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锭沟,一起剝皮案震驚了整個(gè)濱河市抽兆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌族淮,老刑警劉巖辫红,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異祝辣,居然都是意外死亡贴妻,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)蝙斜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)名惩,“玉大人,你說(shuō)我怎么就攤上這事孕荠∶漯模” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵稚伍,是天一觀(guān)的道長(zhǎng)弯予。 經(jīng)常有香客問(wèn)我,道長(zhǎng)个曙,這世上最難降的妖魔是什么锈嫩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮垦搬,結(jié)果婚禮上祠挫,老公的妹妹穿的比我還像新娘。我一直安慰自己悼沿,他們只是感情好等舔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著糟趾,像睡著了一般慌植。 火紅的嫁衣襯著肌膚如雪甚牲。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天蝶柿,我揣著相機(jī)與錄音丈钙,去河邊找鬼。 笑死交汤,一個(gè)胖子當(dāng)著我的面吹牛雏赦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芙扎,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼星岗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了戒洼?” 一聲冷哼從身側(cè)響起俏橘,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎圈浇,沒(méi)想到半個(gè)月后寥掐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡磷蜀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年召耘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褐隆。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡污它,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妓灌,到底是詐尸還是另有隱情,我是刑警寧澤蜜宪,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布虫埂,位于F島的核電站,受9級(jí)特大地震影響圃验,放射性物質(zhì)發(fā)生泄漏掉伏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一澳窑、第九天 我趴在偏房一處隱蔽的房頂上張望斧散。 院中可真熱鬧,春花似錦摊聋、人聲如沸鸡捐。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)箍镜。三九已至源祈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間色迂,已是汗流浹背香缺。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留歇僧,地道東北人图张。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像诈悍,于是被迫代替她去往敵國(guó)和親祸轮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容