Java泛型04 : 泛型类型擦除

超级通道: Java泛型学习系列-绪论

本章主要对Java泛型的类型擦除进行学习。

经过前面几个章节的学习,可以掌握泛型的基本用法,但是有可能这种掌握只是死记硬背。
为了从根本上掌握泛型的使用,我们需要正确的理解泛型,为了正确的理解泛型,我们需要首先理解类型擦除(Type Erasure)的概念。

1.类型擦除初识

先看Java泛型02 : 泛型原始类型、泛型类型命名规范、有界类型的一个例子。

/**
 * <p>Title: 泛型原始类型使用</p>
 * @author 韩超 2018/2/22 11:21
 */
public static void main(String[] args){
    //泛型原始类型
    MyGenericsType myGenericsType = new MyGenericsType();
    LOGGER.info(myGenericsType.getClass().toString());

    //泛型类型
    MyGenericsType<Integer> integerMyGenericsType = new MyGenericsType<Integer>();
    LOGGER.info(integerMyGenericsType.getClass().toString());
}

我们对这个例子进行修改,如下:

//类型擦除
LOGGER.info("类型擦除:");
MyGenericsType myGenericsType = new MyGenericsType();
MyGenericsType<Integer> integerMyGenericsType = new MyGenericsType<Integer>();
MyGenericsType<Double> doubleMyGenericsType = new MyGenericsType<Double>();
LOGGER.info(myGenericsType.getClass().toString());
LOGGER.info(integerMyGenericsType.getClass().toString());
LOGGER.info(doubleMyGenericsType.getClass().toString());

运行结果:

2018-02-23 09:41:53 INFO  TypeErasureDemo:22 - 类型擦除:
2018-02-23 09:41:53 INFO  TypeErasureDemo:26 - class pers.hanchao.generics.type.MyGenericsType
2018-02-23 09:41:53 INFO  TypeErasureDemo:27 - class pers.hanchao.generics.type.MyGenericsType
2018-02-23 09:41:53 INFO  TypeErasureDemo:28 - class pers.hanchao.generics.type.MyGenericsType

通过观察运行结果,可以发现,泛型在编译之后,只保留了原始类型,即MyGenericsType

泛型类型擦除:泛型在编译阶段,生成的字节码中不包含泛型类型,只保留原始类型。

2.类型擦除的原始类型

上面说到,类型擦除之后,只保留原始类型,那么原始类型到底是什么类型呢?
看下面一段代码:

/**
 * <p>Title: 类型擦除之后的原始类型 示例</p>
 * @author 韩超 2018/2/23 10:32
 */
static class TempList<T>{
    private T t;

    public T setT(T t) {
        this.t = t;
        return t;
    }
}

public static void main(String[] args) {
    //类型擦除之后的原始类型
    System.out.println();
    LOGGER.info("泛型类型擦除之后的原始类型:");
    TempList<Integer> tempList = new TempList<Integer>();
    LOGGER.info(tempList.getClass().getDeclaredField("t").getType());
    Method[] methods = tempList.getClass().getDeclaredMethods();
    for (Method method : methods) {
      LOGGER.info(method.getReturnType() + " " + method.getName() + " (" + method.getParameterTypes()[0] + ")");
    }
}

运行结果:

2018-02-23 10:32:18 INFO  TypeErasureDemo:43 - 类型擦除之后的原始类型:
2018-02-23 10:32:18 INFO  TypeErasureDemo:45 - class java.lang.Object
2018-02-23 10:32:18 INFO  TypeErasureDemo:48 - class java.lang.Object setT (class java.lang.Object)

可以肯定:无界的泛型类型擦除之后的原始类型是Object类型
上面的TempList经过编译阶段的类型擦除,形成的原始类型如下:

static class TempList{
    private Object t;

    public Object setT(Object t) {
        this.t = t;
        return t;
    }
}

那么有界的泛型类型呢?
编写示例如下:

/**
 * <p>Title: 有界泛型类型 类型擦除之后的原始类型 示例 </p>
 * @author 韩超 2018/2/23 10:36
 */
static class DemoList<T extends Number>{
    private T t;
    public T setT(T t){
        this.t = t;
        return t;
    }
}
public static void main(String[] args) {
        //有界泛型类型擦除之后的原始类型
    System.out.println();
    LOGGER.info("有界泛型类型擦除之后的原始类型:");
    DemoList<Integer> demoList = new DemoList<Integer>();
    LOGGER.info(demoList.getClass().getDeclaredField("t").getType());
    Method[] methods = demoList.getClass().getDeclaredMethods();
    for (Method method : methods) {
        LOGGER.info(method.getReturnType() + " " + method.getName() + " (" + method.getParameterTypes()[0] + ")");
    }
    LOGGER.info("有界泛型类型擦除之后的原始类型:父类型");
}

运行结果:

2018-02-23 10:41:51 INFO  TypeErasureDemo:70 - 有界泛型类型擦除之后的原始类型:
2018-02-23 10:41:51 INFO  TypeErasureDemo:72 - class java.lang.Number
2018-02-23 10:41:51 INFO  TypeErasureDemo:75 - class java.lang.Number setT (class java.lang.Number)
2018-02-23 10:41:51 INFO  TypeErasureDemo:77 - 有界泛型类型擦除之后的原始类型:父类型

结论:有界泛型类型擦除之后的原始类型:父类型


那么多重有界的泛型类型呢?
编写示例如下:


/**
 * <p>Title: 多重 有界泛型类型 类型擦除之后的原始类型 示例</p>
 * @author 韩超 2018/2/23 10:43
 */
static class AList<T extends Serializable & Comparable>{
    private T t;
    public T setT(T t){
        this.t = t;
        return t;
    }
}
static class BList<T extends Comparable & Serializable>{
    private T t;
    public T setT(T t){
        this.t = t;
        return t;
    }
}
public static void main(String[] args){
    //多重有界泛型类型擦除之后的原始类型
    System.out.println();
    LOGGER.info("多重有界泛型类型擦除之后的原始类型:");
    AList<Integer> aList = new AList<Integer>();
    LOGGER.info(aList.getClass().getDeclaredField("t").getType());
    Method[] methods = aList.getClass().getDeclaredMethods();
    for (Method method : methods) {
        LOGGER.info(method.getReturnType() + " " + method.getName() + " (" + method.getParameterTypes()[0] + ")");
    }
    BList<Integer> bList = new BList<Integer>();
    LOGGER.info(bList.getClass().getDeclaredField("t").getType());
    Method[] methods2 = bList.getClass().getDeclaredMethods();
    for (Method method : methods2) {
        LOGGER.info(method.getReturnType() + " " + method.getName() + " (" + method.getParameterTypes()[0] + ")");
    }
    LOGGER.info("多重有界泛型类型擦除之后的原始类型:第一个父类型");
}

运行结果:

2018-02-23 10:47:38 INFO  TypeErasureDemo:100 - 多重有界泛型类型擦除之后的原始类型:
2018-02-23 10:47:38 INFO  TypeErasureDemo:102 - interface java.io.Serializable
2018-02-23 10:47:38 INFO  TypeErasureDemo:105 - interface java.io.Serializable setT (interface java.io.Serializable)
2018-02-23 10:47:38 INFO  TypeErasureDemo:108 - interface java.lang.Comparable
2018-02-23 10:47:38 INFO  TypeErasureDemo:111 - interface java.lang.Comparable setT (interface java.lang.Comparable)
2018-02-23 10:47:38 INFO  TypeErasureDemo:113 - 多重有界泛型类型擦除之后的原始类型:第一个父类型

结论:多重有界泛型类型擦除之后的原始类型:第一个父类型

3.先检查后编译

既然泛型会在编译阶段进行类型擦除,那如何保证编译之前的类型转换安全呢?答案是:通过IDE提供的编译前检查功能
再看下面一段代码:

//类型擦除与编译前检查
LOGGER.info("类型擦除与编译前检查:");
MyGenericsType<Integer> integerMyGenericsType1 = new MyGenericsType<Integer>();
//类型检查通过
integerMyGenericsType1.setT(new Integer(1));
//类型检查不通过,在IDE中报错
integerMyGenericsType1.setT(new Double(2D));

上述代码在IDE(Integrated Development Environment,集成开发环境)中,无法通过编译前检查,会直接报错,如下:
这里写图片描述

总结:Java泛型先检查在编译

4.通过反射跳过类型检查

通过上面的章节已知,无法将Double类型的数据写入到Integer类型的泛型中,那么久真的无法实现吗?
其实是可以的:通过Java反射可以跳过泛型的类型检查

//通过反射跳过类型检查
LOGGER.info("通过反射跳过类型检查:");
MyGenericsType<Double> doubleMyGenericsType1 = new MyGenericsType<Double>();
//类型检查通过
doubleMyGenericsType1.setT(new Double(2D));
//类型检查不通过
//doubleMyGenericsType.setT(new Integer(1));
//通过反射跳过类型检查
doubleMyGenericsType1.getClass().getMethod("setT",Object.class).invoke(doubleMyGenericsType1,new Integer(1));
LOGGER.info(doubleMyGenericsType1.getT());

result

2018-02-23 09:57:40 INFO  TypeErasureDemo:41 - 通过反射跳过类型检查:
2018-02-23 09:57:40 INFO  TypeErasureDemo:49 - 1

上面的示例,通过反射将Integer类型的数据写入了Double类型的泛型对象中,并且能够get出来,类型为Integer。
那么实际上doubleMyGenericsType1存储的是什么类型呢?
我们运行如下代码:

LOGGER.info(doubleMyGenericsType1.getT().getClass().toString());

会报错:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Double
    at pers.hanchao.generics.erasure.TypeErasureDemo.main(TypeErasureDemo.java:50)

通过报错分析,实际doubleMyGenericsType1实际存储的是Integer类型。

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值