首页 > 范文大全 > 正文

解析Java泛型的类型擦除

开篇:润墨网以专业的文秘视角,为您筛选了一篇解析Java泛型的类型擦除范文,如需获取更多写作素材,在线客服老师一对一协助。欢迎您的阅读与分享!

摘要:泛型是Java SE 1.5的新特性,从表面上看,无论语法还是应用的环境,泛型类型(或者泛型)都类似于C++中的模板,但实际上它们之间有着本质的区别,其本质区别就在于Java泛型的类型擦除,本文重点介绍Java泛型的类型擦除以解决泛型中常见的令人迷惑的和使人头疼的问题。

关键词:Java;泛型;类型擦除

中图分类号:TP312.1 文献标识码:A文章编号:1007-9599(2012)01-0000-02

Analysis of Java Generic Type Erasure

Zhang Dongjiao,Chen Yan,Meng Qingwei

(Dalian Fishe Ries University,Dalian Vocational&Technical College,Dalian116300,china)

Abstract:Generics are the new characteristics of Java SE 1.5.On the surface,whether grammar or application environment,generic types (or generics) are similar to the template in c++,but in fact they are different in essence,the essential difference which lies in Java is a generic type erasure.This paper introduces the Java generic type erasure to solve common confusing and make the person headache.

Keywords:Java;Generics;Type erasure

一、Java泛型的本质

Java虚拟机中并没有泛型类型对象,所有的对象都是一样的,都属于普通的类。由于JVM根本不支持泛型类型,是编译器“耍了个花招”,使得似乎存在对泛型类型的支持,它们用泛型类型信息检查所有的代码,但随即“擦除”所有的泛型类型并生成只包含普通类型的类文件。C#泛型实现的是类型膨胀,即真实泛型,C#里面泛型无论在程序源码中、编译后的IL中或是运行期的CLR中都是切实存在的,List与List就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据。而Java语言中的泛型则不一样,实现的是类型擦除,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,ArrayList与ArrayList就是同一个类ArrayList,Integer和String被擦除了。如:

List integer = new ArrayList();

List string = new ArrayList();

JVM会去解析泛型语法实现擦除,擦除后语句变成了原始类型:

List integer = new ArrayList();

List string = new ArrayList();

ArrayList和ArrayList类型的实例是相同的类,如:

integer.getClass() = =string.getClass()

编译器只为ArrayList生成一个类。所以说java泛型技术实际上是Java语言的一个迷惑,JVM对泛型的编译没有真正使用泛型的标准原理来实现。

二、类型擦除原则

正确理解泛型概念的首要前提是理解类型擦除,擦除不是一个语言特性,它是Java的泛型实现中的一种折中,因为泛型不是Java语言出现时就有的组成部分,所以这种折中是必需的,目的是为了支持向后兼容性,即现有的代码和类文件仍旧合法,并且继续保持其之前的含义。正因为泛型实现了类型擦除,因此泛型类型不能在某些重要的上下文环境中使用,而只能在静态类型检查期间才出现,并在此之后,程序中的所有泛型类型都将被擦除,替换为相应的非泛型。泛型擦除的基本过程体现为以下几个原则:

(1)所有参数化容器类都被擦除成非参数化的(raw type);如List、List都被擦除成List;

(2)所有参数化数组都被擦除成非参数化的数组;如List[],被擦除成List[];

(3)Raw type的容器类,被擦除成其自身,如List被擦除成List;

(4)原生类型(int,String还有wrapper类)都擦除成他们的自身;

(5)参数类型E,如果没有上限,则被擦除成Object;

(6)所有约束参数如

(7)如果有多个约束,擦除成第一个,如,则擦除成Object;

由于擦除的实现,减少了泛型的泛化性,所有的强制转换都为自动和隐式的,提高了代码的重用率。但擦除机制也导致了一些有用的类型信息丢失,因此在使用时,泛型存在一些约束和限制。

三、擦除产生的问题

擦除是一种很巧妙的办法,但它有时候会带来一些意想不到的错误:两个看上去并不相同的泛型类或是泛型方法,由于擦除的作用,最后会得到相同的类和方法。这种错误,也被称为冲突。冲突主要发生在下述三种情况。

(一)静态成员共享问题

List in= Arrays.asList(1,2,3);

List str = Arrays.asList("one","two");

assert in.getClass() == str.getClass();

in和str两个对象最终被擦除成具有相同类型的(List)的对象,于是这两个对象共享List的静态成员,于是就可以得出这样的结论,所有泛化类型的静态成员被其所有的实例化对象共享,因此也就要求所有静态成员不能够是泛化的。

class Coo {

static T value1; //错误

private T value2; //正确

private static List value3 = new ArrayList(); //错误

public T getValue()

{ return value; }

public static List value4 = new ArrayList() //正确

}

出现错误的两个变量value1和value3都采用不同的形式使用了类型参数T。由于它们是静态成员,是独立于任何对象的,也可以在对象创建之前就被使用。此时,编译器无法知道用哪一个具体的类型来替代T,所以编译器不允许这样使用。

(二)重载冲突问题

在一个类的范围内,如果两个函数具有相同的函数名称,不同的参数(返回值不考虑)就互相称为重载函数。擦除带来的另外一个问题是重载的冲突,下面是两个方法的重载:

void conflict(T o){}

void conflict(Object o){}

由于在编译时,T会被Object所取代,所以这两个实际上声明的是同一个方法,重载就出错了。

(三)接口实现问题

由于接口也可以是泛型接口,而一个类又可以实现多个泛型接口,所以也可能会引发冲突。

class Coo implements Comparable, Comparable

由于Comparable、Comparable都被擦除成Comparable,所以这实际上是实现的同一个接口。如:

class Coo implements Comparable

如果要实现多个泛型接口,只能实现具有不同擦除效果的接口。

四、类型擦除的限制

因为Java并没有真正实现泛型,而采用擦除机制,造成了Java泛型本身有很多漏洞。为了规避这些问题,Java在泛型的使用上除了对上述三种冲突进行约束外,还做了其它的限制。下面是其中一些限制:

(一)不能用泛型类型参数创建实例对象,如:E object=new E();。因为由于类型擦除,编译时所有泛型类型都会相应的变为它的原始类型,这时泛型E是不可用的。

(二)不能在数据类型转换或instanceof操作中使用类型参数。

(三)不能用基本类型替换类型参数,如:List。基本类型不属于对象,类型擦除后不能被Object或者限定类型替换,但如果要将基本类型应用到泛型当中,可以用基本类型的包装类。

(四)不能将泛型用在异常捕获和用泛型来实现Throwable子类,如:try{}catch (MyException){}。因为泛型不能扩展java.lang.Throwable,这样做将出现编译错误。

(五)不能使用泛型类创建数组。如:ArrayList[] list=new ArrayList[10];。因为如果先创建一个类型数组而后转型为Object或是先声明类型数组而后创建一个Object数组通过强制类型转换来实现类型数组都将导致失败或者是类型擦除方面的问题。

总结:

泛型是Java语言走向类型安全的一大步,它提升了Java代码的健壮性和易用性,但SUN本身过分强调向前的兼容性,引入了擦除,由此产生了不少问题和麻烦。本文重点让大家认识类型擦除的本质,了解擦除产生的问题及解决方法,旨在帮助大家更深入的学习泛型。

参考文献:

[1]Sky-Tiger,JAVA泛型浅析,,2009

[作者简介]

张冬姣(1977-),女,大连海洋大学职业技术学院,讲师,主要研究计算机软件