类型擦除引起的问题及解决方法

300 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第43天,点击查看活动详情

类型检测针对谁?

public static  void main(String[] args) {
	ArrayList<String> arrayList=new ArrayList<String>();
	arrayList.add("123");
	arrayList.add(123);//编译错误
}

类型擦除后,原始类型为Object,是应该运行任意引用类型的添加的。可实际上却不是这样,这恰恰说明了关于泛型变量的使用,是会在编译之前检查的。

那么,这么类型检查是针对谁的呢?我们来看例子:

public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add(1); //编译报错

        ArrayList<String> arrayList1 = new ArrayList(); //第一种 情况
        arrayList1.add(1); //编译报错

        ArrayList arrayList2 = new ArrayList<String>();//第二种 情况
        arrayList2.add(1);
    }

通过上面的例子,我们可以明白,类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。

自动类型转换

因为类型擦除的问题,所以所有的泛型类型变量最后都会被替换为原始类型。这样就引起了一个问题,既然都被替换为原始类型,那么为什么我们在获取的时候,不需要进行强制类型转换呢?

我么来看一下List的get()方法:

image.png

可以看到基本各个类都已经自动帮你转了。

类型擦除与多态的冲突和解决方法

这个其实是类型擦除引起的最大的问题了,代码如下。

image.png

实际上,从他们的@Override标签中也可以看到,在子类中重写这两个方法一点问题也没有,实际上是这样的吗?

分析:

泛型擦除后,父类是下面这样子

class Generic {
    //key这个成员变量的类型为T,T的类型由外部指定
    private Object var;

    public Object getVar() {
        return var;
    }

    public void setVar(Object var) {
        this.var = var;
    }
}

子类还是这样

class MyGeneric extends Generic<Integer>{
    @Override
    public Integer getVar() {
        return super.getVar();
    }
    @Override
    public void setVar(Integer var) {
        super.setVar(var);
    }
}

先来分析setValue方法,父类的类型是Object,而子类的类型是Date,参数类型不一样,这如果实在普通的继承关系中,根本就不会是重写,而是重载。

重载(Overload):首先是位于一个类之中或者其子类中,具有相同的方法名,但是方法的参数不同,返回值类型可以相同也可以不同。

(1):方法名必须相同。

(2):方法的参数列表一定不一样。

(3):访问修饰符和返回值类型可以相同也可以不同。

重写(override):一般都是表示子类和父类之间的关系,其主要的特征是:方法名相同,参数相同,但是具体的实现不同。

重写的特征:

(1):方法名必须相同,返回值类型必须相同

(2):参数列表必须相同

(3):访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。

(4):子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。

(5):构造方法不能被重写

我们来测试下到底是重载还是重写

image.png 如果是重载的话,第四行代码是不会报错的,因为调的是不同的重载方法。但是发现编译报错了,也就是说没有参数是Object的这样的重载函数。所以说是重写了,导致MyGeneric对象只能调用自己重写的方法。

为什么会这样呢?

原因是这样的,我们传入父类的泛型类型是Integer,Generic,我们的本意是将泛型类变为如下:

class Generic {
    //key这个成员变量的类型为T,T的类型由外部指定
    private Integer var;

    public Integer getVar() {
        return var;
    }

    public void setVar(Integer var) {
        this.var = var;
    }
}

然后再子类中重写参数类型为Date的那两个方法,实现继承中的多态。

​ 可是由于种种原因,虚拟机并不能将泛型类型变为Integer,只能将类型擦除掉,变为原始类型Object。这样,我们的本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突。JVM知道你的本意吗?知道,可是它能直接实现吗,不能。如果真的不能的话,那我们怎么去重写我们想要的Integer类型参数的方法啊。

JVM采用了一个特殊的方法,来完成这项功能,那就是桥方法