java泛型的擦除_谈谈java泛型的擦除机制

历史原因:在JDK5.0之前,容器存储的对象都只具有java的通用类型:Object。单根继承结构意味着所有东西都是Object类型,所有该容器可以存储任何东西,但是由于容器只存储Object,所以当将对象引入容器时,它必须被向上转型为Object,因此它丢失了身份,将它取出时取到的是一个Object引用,必须将该Object向下转型为更具体的类型。这种转型方式称为向下转型。向上转型是安全的,但是向下转型是不安全的。

那么是否能创建一个容器,它知道自己所保存的对象类型,从而不需要向下转型以及消除犯错误的可能。

这样的解决方案被称为参数化类型机制,参数化类型就是一个编译器可以自动定制作用于特定类型上的类。

这正是SE5.0 的重大变化之一,就是增加了参数化类型,在java中称为泛型。泛型的核心概念:告诉编译器想使用什么类型,编译器帮你处理一切细节。

擦除的神秘之处:

当你深入研究泛型时,你会发现有大量的东西初看起来是没有意义的。例如,尽管可以声明为ArrayList.class,但是不能声明为ArrayList.class。因为声明为ArrayList.class 中的 是没有任何意义的。在编译器后是没有的,是被擦除的。

java泛型是使用擦除来实现的,这意味着当你在使用泛型的时,任何具体的  类型信息  都被擦除了,你唯一知道的就是你在使用一个对象。因此List和List在运行时  事实上是相同的类型的。这两种形式都被擦除成它们的"原生"的类型

对比C++的模板:

template class Manipulator {

T obj;

public:

Manipulator(T x) {  obj = x  }

void manipulate() {  obj.f()  }

};

class HasF {

public:

void f() {...}

}

Manipulator类存储了一个类型T的对象,有意思的是manipulate()方法,它在obj上调用方法f()。

那它是怎么知道f()方法是为类型参数T而存在的?原理是这样的:

·当你实例化这个模板时,C++编译器将进行检查,因此在Manipulator被实例化的这一刻,它就看到了HasF拥有一个方法f(),如果情况不是这样,就会出现一个编译错误, 这样类型安全就等到了保障。用C++编写这样的代码很简单,因为当模板被实例化时,模板代码就知道了其模板参数的类型了

为了迁移兼容性 :

1.为了减少潜在的关于 擦除 的混乱,你必须认识到这不是一个语言的特征,它是java泛型实现中的一种折中。因为泛型不是java语言出现时就有的组成成分,所以这种折中是必须的

2.如果泛型是java1.0就已经是其一部分了,那么这个特征将不会使用擦除来是实现--它将使用具体化,使类型参数保持为第一类实体,这样的话你就可以在类型参数上执行基于类型语言的操作和反射操作。擦除  减少了泛型的泛化性,泛型咋java中仍然是有用的,只是不如它们本来设想的那么有用,其原因就是擦除

3.在基于擦除的实现中,泛型类型被当作第二类类型来处理,既不能在重要的上下文环境中使用 类型。泛型类型只有在  静态类型  检查期间才出现,在此之后,程序中所有的泛型类型都被擦除了,替换为它们的非泛型上界。例如:诸如List这样的类型注解将被擦除为List,而普通的类型变量在未指定边界的情况下将被擦除为Object

4.擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然。这经常被称为"迁移兼容性"。因为在理想情况下,当所有的事物都可以使用泛化,那我们就可以专注于此。但是在现实中,5.0之前是没有泛型的,必须要处理这些没有被泛化的类库

5.因此java泛型不仅必须支持向后兼容性,即现有的代码是合法的,并且继续保持之前的含义。而且还要支持迁移兼容性,使得旧的代码不会影响新的代码。一句话就是允许非泛型代码与泛型代码共存,擦除 使得这种向着泛型的迁移成为可能

擦除的问题

1.擦除的主要的正当理由是从非泛化代码到泛化代码的转变过程,以及在不破环现有类库的情况下,将泛型融入java语言。擦除使得现有的非泛型客户端代码能够在不改变的情况继续使用直到客户端准备好用泛型重写这些代码。这是一个崇高的动机,因为他不会突然间破环所有现有的代码

2.擦除的代价是显著的,泛型不能用于显式地引用运行时类型的操作之中。因为所有关于参数的类型信息都丢失了。

3.无论何时,当你在编写泛型代码的时候,必须时刻提醒自己,你只是看起来 好像拥有了 有关类型的信息。其实它只不过是一个Object而已

4.泛型不是强制的

5.当你希望将类型参数不要仅仅当作是Obbject处理时,就需要付出额外努力来管理边界(例如通过编写这些来管理)

6.·其他编程语言的参数化类型相比(例如C++):通常比java得心应手,它们的参数化类型机制比java的更灵活,更强大

边界处的动作:因为擦除,泛型最令人困惑的一方面源自这样的一个事实,即可以表示没有任何意义的事物

1.编译器是无法知道你具体的类型信息,但是它可以确保你放置的对象具有 T 类型。因此,即使擦除了有关实际类型的信息,编译器是可以确保 使用 类型的一致性。(就是它不知道你是什么类型,但是却可以确保在编译期间类型的一致性,否则编译时通过不了的)

2因为擦除在方法体中移除了类型信息,所以在运行时的问题就是边界:即对象进入和离开方法的地点。这正是编译器在编译期执行了 类型检查 并   插入转型代码   的地点。实际上就算你用了泛型,也是要进行强制类型转换的,但由于 泛型代码 在编译期确保了 类型的一致性,所以它允许你不用显式的编写 转型代码,而是编译器 自动帮你处理(编译器帮你进行类型转换了)。

2.因此你编写的代码噪声将更小。

3.所以在泛型中的所有动作都发生在边界处---对传递进来的值进行额外的编译期检查,并对传递出去的值进行转型。记住,"边界就是发生动作的地方

擦除的补偿:擦除丢失了在泛型代码中执行某些操作的能力

要调用new T()的尝试将会无法实现的,部分原因是因为擦除,而另一部分的原因是因为编译器不能验证T是否具有默认(无参)的构造器,但是在C++中,这种操作很自然,很直观,并且很安全(它是在编译期进行检查),java的解决方案是传递一个工厂方法,并使用它来创建新的实例。最便宜的工厂对象就是Class对象,因此如果使用类型标签,那么你就可以使用newInstance()来创建这个类型的新对象

上面观点大部分是源自编程思想,还记得里面有一句话,java选择泛型的擦除是一种中庸之道。如果重新设计java未必还会这样做。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值