最近看了《thinking in java》的第十五章泛型,感觉有些东西需要记录下来。
泛型是Java SE5才被引入的概念,现在我的工作中泛型主要使用在集合,这样可以知道set()和get()的类型(类型检查是在编译阶段,可以使用反射绕过编译),而不必再进行额外的转型操作。
今天,我们主要是来聊一聊Java泛型的擦除。
或许,你对Java泛型的擦除的概念不是很理解,下面我们来看一个例子:
很多人会认为ArrayList<String>和ArrayList<Integer>是不同的类型,但是这段程序打印的结果是true。
下面我们再来看一个例子:
这种代码在Java中是不能编译通过的,即使我传的是baby这个类的对象。但是这种代码在C++中不仅能编译通过还能执行。
这种在泛型代码内部获取不到有关泛型参数类型的一种泛型实现并不是Java的语言特性,而是Java泛型实现的一种折中。
Java的泛型是使用擦除实现的。这也就意味着当你在使用泛型的时候,任何具体的类型信息都被擦除了,你唯一知道的是你在使用一个对象。就如同上例的ArrayList<String>和ArrayList<Integer>在运行是相同的类型,它们都被擦出成它们的原生类型,List。
如果,泛型在Java 1.0中就已经是其中一部分,那么这个特性很可能就不会有擦除来实现,将会使用具体化,使类型参数保持一致,因此就可以在类型参数上执行基于类型语言的操作和反射操作。
同样,擦除的代价也是巨大的。泛型不能用于显式的引用运行时类型的操作之中,例如转型,instanceof和new。
即使,擦除在方法体内移除了有关实际类型的信息,编译器仍能够确保在方法或者类中使用的类型的内部一致性。以为擦除在方法体内移除了类型信息,所以运行时的问题的就是边界:对象进入和离开的方法的地点。这些就是编译器在编译期执行类型检查并插入转型代码的地点。下面的例子很好的说明了这点。
下面是用Javap -c Test反编译这个类看到的内容
set()和get()方法将直接存储和产生值,转型是在调用get()的时候接受检查的。
下面我们用上泛型再看看,
反编译的内容如下:
所产生的字节码相同,对进入set()的类型检查是不需要的,因为这由编辑器执行。而对从get()返回的值仍需进行转型。
所以在泛型中的所有动作都发生在边界处。