何时发生
我在Oracle的网站上阅读了关于Java的类型删除。
类型擦除何时发生? 在编译时/运行时? 当类加载/运行时? 当类被实例化时?
很多站点(包括上面提到的Sun教程)都说编译时会发生类型擦除。 如果类型信息在编译时被完全删除,那么当调用没有类型信息或错误类型信息的使用泛型的方法时,JDK检查类型兼容性如何。
考虑下面的例子:说A类有一个方法,为empty(Box<? extends Number> b)
。 我们编译A.java并获取类文件A.class。
现在我们创建另一个B类,它使用非参数化参数(原始类型) - empty(new Box())
调用空方法。 如果我们在类路径中使用A.class编译B.java,javac足够聪明,可以引发警告。 所以A.class中存储了一些类型信息。
我的猜测是类加载时发生的类型擦除,但这只是一个猜测。 那么它何时发生?
类型删除适用于泛型的使用。 在类文件中有明确的元数据来说明方法/类型是否是泛型的,以及约束是什么等。但是当使用泛型时,它们会转换为编译时检查和执行时间转换。 所以这段代码:
List<String> list = new ArrayList<String>();
list.add("Hi");
String x = list.get(0);
被编译进去
List list = new ArrayList();
list.add("Hi");
String x = (String) list.get(0);
在执行时,没有办法找出列表对象的T=String
- 该信息已消失。
...但List<T>
接口本身仍然宣称自己是通用的。
编辑:只是为了澄清,编译器确实保留有关作为List<String>
的变量的信息 - 但你仍然无法找到列表对象本身的T=String
。
编译器负责在编译时理解泛型。 编译器还负责抛弃泛型类的这种“理解”,在我们称之为类型擦除的过程中。 全部发生在编译时。
注意:与大多数Java开发人员的看法相反,尽管使用非常有限,但仍可以在运行时保留编译时类型信息并检索这些信息。 换句话说, Java确实以非常有限的方式提供了泛化的泛型 。
关于类型删除
请注意,在编译时,编译器具有可用的完整类型信息,但是在生成字节代码时,通常故意删除此信息,这种过程称为类型擦除。 这是由于兼容性问题以这种方式完成的:语言设计者的意图是提供完整的源代码兼容性和平台版本之间的全字节代码兼容性。 如果实现方式不同,则在迁移到较新版本的平台时,必须重新编译旧应用程序。 完成的方式,保留了所有的方法签名(源代码兼容性),并且不需要重新编译任何东西(二进制兼容性)。
关于Java中的泛化泛型
如果您需要保留编译时类型信息,则需要使用匿名类。 重点是:在匿名类的特殊情况下,可以在运行时检索完整的编译时类型信息,换句话说,这意味着:泛化的泛型。 这意味着编译器在涉及匿名类时不会丢弃类型信息; 此信息保存在生成的二进制代码中,运行时系统允许您检索此信息。
我写了一篇关于这个主题的文章:
http://rgomes-info.blogspot.co.uk/2013/12/using-typetokens-to-retrieve-generic.html
关于上述文章中描述的技术的一个注意事项是,这项技术对于大多数开发人员来说是模糊的。 尽管它运作良好,但大多数开发人员对这项技术感到困惑或不舒服。 如果您有共享的代码库或计划向公众发布您的代码,我不推荐上述技术。 另一方面,如果您是代码的唯一用户,则可以利用此技术为您提供的强大功能。
示例代码
上面的文章有示例代码的链接。
如果您有一个通用类型的字段,则其类型参数将被编译到该类中。
如果你有一个接受或返回一个泛型类型的方法,那些类型参数被编译到这个类中。
此信息是编译器用来告诉您无法将Box<String>
传递给empty(Box<T extends Number>)
方法的信息。
API很复杂,但您可以使用getGenericParameterTypes
, getGenericReturnType
等方法通过反射API检查此类型信息,并且对于字段getGenericType
。
如果您有使用泛型类型的代码,编译器会根据需要(在调用者中)插入强制类型来检查类型。 通用对象本身只是原始类型; 参数化类型被“擦除”。 所以,当你创建一个new Box<Integer>()
, Box
对象中没有关于Integer
类的信息。
Angelika Langer的常见问题解答是我见过的关于Java泛型的最佳参考。
链接地址: http://www.djcxy.com/p/73415.html