关于泛型的类型擦除

关于泛型的类型擦除

什么是泛型?

在探讨类型擦除之前,我们还是先了解一下,泛型的概念。

泛型是为了参数化类型,定义方法时传入形参,而调用方法时使用形参,参数化类型就是由确定的类型参数化,改为不确定的类型,而在调用时使用具体类型的参数,从而实现解耦。这种参数类型可以用于类、接口、方法中,分别被称为泛型类、泛型接口和泛型方法。

为什么使用泛型

参考文章

这里的栗子是别人写的,个人觉得挺形象的,粘贴一下

试想你需要一个简单的容器类,或者说句柄类,比如要存放一个苹果的篮子,那你可以这样简单的实现:

class Fruit{}
class Apple extends Fruit{}

class Bucket{
    private Apple apple;
   
     public void set(Apple apple){
        this.apple = apple;
    }
   
    public Apple get(){
        return this.apple;
    }
}

这样一个简单的篮子就实现了,但问题是它只能存放苹果,之后又出现了另外的一大堆水果类,那你就不得不为这些水果类分别实现容器:

class Fruit{}
class Apple extends Fruit{}
class Banana extends Fruit{}
class Orange extends Fruit{}

class BucketApple{
    private Apple apple;

    public void set(Apple apple){
        this.apple = apple;
    }

    public Apple get(){
        return this.apple;
    }
}

class BucketBanana{
    private Banana banana;

    public void set(Banana banana){
        this.banana = banana;
    }

    public Banana get(){
        return this.banana;
    }
}
class BucketOrange{
    private Orange orange;

    public void set(Orange orange){
        this.orange = orange;
    }

    public Orange get(){
        return this.orange;
    }
}

然后你发现你其实在做大量的重复劳动。所以你幻想你的语言编译器要是支持某一种功能,能够帮你自动生成这些代码就好了。

不过在祈求让编译器帮你生成这些代码之前,你突然想到了Object能够引用任何类型的对象,所以你只要写一个Object类型的Bucket就可以存放任何类型了。

class Bucket{
    private Object object;

    public void set(Object object){
        this.object = object;
    }

    public Object get(){
        return this.object;
    }
}

但是问题是这种容器的类型丢失了,你不得不在输出的地方加入类型转换:

Bucket appleBucket = new Bucket();
bucket.set(new Apple());
Apple apple = (Apple)bucket.get();

而且你无法保证被放入容器的就是Apple,因为Object可以指向任何引用类型。

这个时候你可能又要祈求编译器来帮你完成这些类型检查了。

说到这里,你应该明白了泛型要保证两件事,

第一:我只需要定义一次类,就可以被“任何”类型使用,而不是对每一种类型定义一个类。

第二:保存对象类型,而不用每次都强转

泛型的实现原理

通过以上的栗子,我们对泛型有一个大致的了解,但是泛型是怎么来实现以上特性的呢?这里就引入这篇文章的标题的内容,‘类型擦除

我这里自己写了一个泛型java类,很简单

public class TestGeneric<T> {
    public T instance;
    public TestGeneric(T hello) {
        instance=hello;
        System.out.println(hello.getClass().getCanonicalName());
    }
}

编译以后生成的class文件

public class TestGeneric<T> {
    public T instance;

    public TestGeneric(T hello) {
        this.instance = hello;
        System.out.println(hello.getClass().getCanonicalName());
    }
}

对,你没看错就是一样的。但是我们把鼠标放到T上,会看到T扩展自Object,所以泛型根本上还是通过Object来实现的类型扩展。

image-20211104134152110

我们接着看以下泛型在调用时的情况

public class TestGenericCore {
    public void show(){
        Apple apple=new Apple();
        Banana banana=new Banana();
        TestGeneric<Apple> appleTestGeneric=new TestGeneric<>(apple);
      //不用强转就可以直接调用指定类的成员方法,这就是泛型的好处
        appleTestGeneric.instance.showApple();
        TestGeneric<Banana> bananaTestGeneric=new TestGeneric<>(banana);
        bananaTestGeneric.instance.showBanana();
    }
}
public class Apple {
    public void showApple(){}
}
public class Banana {
    public void showBanana(){}
}

这里我们分别传入两个类,并且为了区分,成员方法分别是showApple和showBanana。

我们接着看一下以上TestGenericCore的class文件是怎么样的。

public class TestGenericCore {
    public TestGenericCore() {
    }

    public void show() {
        Apple apple = new Apple();
        Banana banana = new Banana();
        TestGeneric<Apple> appleTestGeneric = new TestGeneric(apple);
        ((Apple)appleTestGeneric.instance).showApple();
        TestGeneric<Banana> bananaTestGeneric = new TestGeneric(banana);
        ((Banana)bananaTestGeneric.instance).showBanana();
    }
}

强转出现……所以泛型最后在编译时也会替我们把隐去的强转加上,可以说是非常贴心了。

至于类型擦除,就是在编译期间,根据我们声明的泛型类型进行静态类型检查,然后再将类型擦除,改为Object。所谓的类型检查就是在对象出现的地方,例如(赋值语句,函数调用的实参和形参检查、return返回的类型必须兼容)判断类型是否符合约束。

因为类型会被擦除,所以以下方法会报错,因为在编译看来,形参是一样的。

image-20211104141122624
总结

好了,本篇只是用来记录一下自己搞懂类型擦除的过程,希望对大家有所帮助。

引用

java为什么要用类型擦除实现泛型

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值