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

Java 泛型(generics)是 JDK1.5 中引入的一个新特性,其本质是参数化类型,解决不确定具体对象类型的问题;其所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

但是在 Java 中并不是真正的泛型,实际上是“伪泛型”

类型擦除(type Erasure)

为了与之前的版本兼容,JDK1.5 中通过类型擦除来增加的泛型功能。Java 泛型只是在编译器层次上,在编译后生成的字节码中是不包含泛型中类型的信息的。
通过一个例子来证明类型擦除

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

上面定义了两个 ArrayList,一个是 ArrayList<String>泛型类型的,一个是 ArrayList<Integer>类型的,但是最后打印的是true,说明两个类型相同。
javap -c看一下生成的生成的字节码

可以看到在字节码中,ArrayList<String>和 ArrayList<Integer>都被编译成了 ArrayList 类型,可见编译后发生了类型擦除。

  1. 既然编译后发生了类型擦除,那么虚拟机解析、反射等场景是怎么获取到正确的类型的?

在 JDk1.5 中增加泛型的同时,JCP 组织修改了虚拟机规范,增加了SignatureLocalVariableTypeTable新属性。
javap -v查看一下字节码,在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是一个可选属性,如果存在泛型,则会出现这个属性。在Signature下包含了泛型的信息。

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

类型擦除之后,当调用sList.get(0)是如何确保返回的值不会和 String 不匹配呢?
javap -c查看一下字节码

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处有一个checkcast指令,checkcast用于检查类型强制转换是否可以进行,也就是泛型在获取值的时候进行了强制类型转换。

  1. 再来看看下面这段代码

首先定义一个 Java 泛型类

public class GenericClass<T> {

    private T value;

    public T getValue() {
        return value;
    }

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

再定义一个子类继承它

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 方法,因为存在类型擦除,父类GenericClass的泛型被擦除了。
javap -c 查看一下GenericClass编译后的字节码

可以看到类型擦除后泛型变为了Object。那么GenericClass也就变为了

public class GenericClass {

    private Object value;

    public Object getValue() {
        return value;
    }

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

这样,父类GenericClass中 set 和 get 方法操作的是 Object 对象,而子类GenericClassTest 操作的是 Integer 对象,为什么还可以重写?按照正常的继承关系中,这应该是重载。
按照重载的方式试一下

可以看到设置 Object 对象出现了红波浪线,不允许这样设置,看来确实是重写,而不是重载。为什么会时重写,这不是跟 Java 多态冲突么?继续往下研究。
现在用javap -c看一下子类GenericClassTest的字节码文件

GenericClassTest中 get 和/set 方法都有两个,一个是操作 Object 对象一个是操作 Integer 对象。
操作 Integer 对象的是GenericClassTest定义的,操作 Object 对象的是由编译器生成的。
再用javap -v 查看一下字节码更详细的信息。

编译器生成的两个操作 Object 对象的方法中多了两个ACC_BRIDGEACC_SYNTHETIC标志。
这就是虚拟机解决类型擦除和多态冲突问题的方法:使用桥接方法
桥接方法方法是由编译器生成的,我们在代码中并不能直接使用,但是可以通过反射拿到桥接方法再使用。

泛型一旦编译过后,类型就被擦除了,那到了运行时,怎么获取泛型信息?这就要使用 JDK 提供的 Type 类型接口了。

Type 类型

在没有泛型之前,所有的类型都通过 Class 类进行抽象,Class 类的一个具体对象就代表了一个类型。
在 JDK1.5 增加了泛型之后,扩充了数据类型,将泛型也包含了。
JDK 在原来的基础上增加了一个Type接口,它是所有类型的父接口,它的子类有

  • Class类: 原始/基本类型,包括平时我们所有的类、枚举、数组、注解,还有 int、float 等基本类型
  • ParameterizedType接口:参数化类型,比如 List<String>
  • TypeVariable接口:类型变量,比如 List<T>中的 T 就是参数化变量
  • GenericArrayType接口: 数组类型,比如 List<String>[]、T[]
  • WildcardType接口:泛型表达式类型,比如 List< ? extends Number>

ParameterizedType

参数化类型,即带有参数的类型,也就是带有<>的类型

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

        Type getRawType();

     Type getOwnerType();
}
  • getActualTypeArguments(): 获取类型内部的参数化类型 比如 Map<K,V>里面的 K,V 类型。
  • getRawType(): 类的原始类型,比如 Map<K,V>中的 Map 类型。
  • getOwnerType(): 获取所有者类型(只有内部类才有所有者,比如 Map.Entry 他的所有者就是 Map),若不是内部类,此处返回 null。

实例:

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 的区别,TypeVariable 代表着泛型中的变量,而 ParameterizedType 则代表整个泛型。比如 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():类型对应的上限,默认为 Object 可以有多个。比如 List< T extends Number & Serializable>中的 Number 和 Serializable
  • getGenericDeclaration(): 获取声明该类型变量实体,比如 GenericClass< T>中的 GenericClass
  • getName():获取类型变量在源码中定义的名称;

实例:

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

泛型数组类型,用来描述 ParameterizedType、TypeVariable 类型的数组;例如:List<T>[] 、T[]、List<String>[]等。

注意: GenericArrayType 是来描述与泛型相关的数组,与 String[]、int[]、float[]这种类型不同。

public interface GenericArrayType extends Type {

    Type getGenericComponentType();
}
  • getGenericComponentType():返回泛型数组中元素的 Type 类型,比如 List<String>[] 中的 List<String>

实例:

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

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

注意: WildcardType 虽然是 Type 的子接口,但不代表一种类型,,表示的仅仅是类似 ? extends T、? super K 这样的通配符表达式。

public interface WildcardType extends Type {

    Type[] getUpperBounds();

    Type[] getLowerBounds();
}
  • getUpperBounds() 获得泛型表达式上界(上限) 获取泛型变量的上边界(extends)
  • getLowerBounds() 获得泛型表达式下界(下限) 获取泛型变量的下边界(super)

实例:

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容