在Java中的generics擦除的概念是什么?

在Java中的generics擦除的概念是什么?

基本上,generics是通过编译器技巧在Java中实现的。 编译的generics代码实际上只是在你谈论T (或者其他一些types参数)的地方使用java.lang.Object ,还有一些元数据告诉编译器它确实是一个genericstypes。

当您针对genericstypes或方法编译某些代码时,编译器会计算出您的真正含义(即T的types参数是什么),并在编译时validation您所做的是正确的事情,以java.lang.Objectforms进行交谈 – 编译器在必要时生成额外的强制转换。 在执行时, List<String>List<Date>完全相同; 编译器删除了额外的types信息。

比较一下,比如说C#,在那里信息在执行时被保留,允许代码包含types为typeof(T)expression式,这与T.class是等价的 – 除了后者是无效的。 (在.NETgenerics和Javagenerics之间还有更多的区别,请注意。)在处理Javagenerics时,types擦除是许多“奇怪”的警告/错误消息的来源。

其他资源:

  • Sun文档
  • 维基百科
  • Gilad Bracha的Javagenerics指南 (PDF – 强烈推荐;链接可能需要定期更改)
  • Angelika Langer的Javagenerics常见问题

正如一个侧面说明,实际上看到编译器在执行擦除时正在做什么是一个有趣的练习 – 使整个概念更易于理解。 有一个特殊的标志,你可以通过编译器来输出已经删除了generics并强制插入的java文件。 一个例子:

 javac -XD-printflat -d output_dir SomeFile.java 

-printflat是传递给生成文件的编译器的标志。 ( -XD部分是告诉javac把它交给可执行的jar,而不是javac ,但是我离题了) -d output_dir是必要的,因为编译器需要放置新的地方。 java文件。

当然,这不仅仅是删除, 所有编译器自动完成的工作都在这里完成。 例如,默认的构造函数也被插入,新的foreach风格的for循环被扩展为常规for循环,等等。很高兴看到自动发生的小事情。

为了完成已经非常完整的Jon Skeet的回答,你必须认识到types擦除的概念源于需要与以前版本的Java兼容的需求。

最初在EclipseCon 2007上展示(不再可用),兼容性包括以下几点:

  • 源代码兼容性(很高兴有…)
  • 二进制兼容性(必须有!)
  • 迁移兼容性
    • 现有的scheme必须继续工作
    • 现有的库必须能够使用genericstypes
    • 一定有!

原始答案:

因此:

 new ArrayList<String>() => new ArrayList() 

有更多的物化命题。 要把“把抽象的概念当作真实的”,这里的语言结构应该是概念,而不仅仅是句法糖。

我还应该提到Java 6的checkCollection方法,该方法返回指定集合的​​dynamictypes安全视图。 任何尝试插入错误types的元素都将导致立即出现ClassCastException

该语言中的generics机制提供了编译时(静态)types检查,但可以通过未经检查的强制转换来打败该机制

通常这不是问题,因为编译器会在所有这些未经检查的操作上发出警告。

然而,有些时候静态types检查本身是不够的,例如:

  • 当一个集合被传递给第三方库时,通过插入错误types的元素,库代码不应该破坏集合。
  • 一个程序失败,出现一个ClassCastException ,表明一个错误types的元素被放入参数化的集合中。 不幸的是,在插入错误元素之后的任何时候都可能发生exception,所以它通常不会提供关于问题的真正来源的信息。

几乎四年后的2012年7月更新:

现在(2012)在“ API迁移兼容性规则(签名testing) ”

Java编程语言使用擦除来实现generics,除了一些有关types的辅助信息之外,它确保了传统和通用版本通常会生成相同的类文件。 二进制兼容性不会被破坏,因为可以使用generics类文件replace旧类文件,而无需更改或重新编译任何客户端代码。

为了方便与非通用遗留代码的接口,也可以使用参数化types的擦除作为types。 这种types被称为原始types ( Java Language Specification 3 / 4.8 )。 允许原始types也确保了源代码的向后兼容性。

据此,以下版本的java.util.Iterator类是二进制和源代码向后兼容:

 Class java.util.Iterator as it is defined in Java SE version 1.4: public interface Iterator { boolean hasNext(); Object next(); void remove(); } Class java.util.Iterator as it is defined in Java SE version 5.0: public interface Iterator<E> { boolean hasNext(); E next(); void remove(); } 

擦除,字面上意味着源代码中存在的types信息从编译的字节码中被擦除。 让我们用一些代码来理解这一点。

 import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class GenericsErasure { public static void main(String args[]) { List<String> list = new ArrayList<String>(); list.add("Hello"); Iterator<String> iter = list.iterator(); while(iter.hasNext()) { String s = iter.next(); System.out.println(s); } } } 

如果你编译这个代码,然后用Java反编译器反编译,你会得到这样的东西。 请注意,反编译的代码不包含原始源代码中存在的types信息的跟踪。

 import java.io.PrintStream; import java.util.*; public class GenericsErasure { public GenericsErasure() { } public static void main(String args[]) { List list = new ArrayList(); list.add("Hello"); String s; for(Iterator iter = list.iterator(); iter.hasNext(); System.out.println(s)) s = (String)iter.next(); } } 

补充已经补充的乔恩Skeet答案…

有人提到,通过删除来实现generics会导致一些令人讨厌的限制(例如,没有new T[42] )。 也有人提到这样做的主要原因是在字节码中向后兼容。 这也是(大部分)是正确的。 生成的字节码-target 1.5与仅仅去糖的cast-target 1.4有所不同。 从技术上讲,甚至有可能(通过巨大的诡计) 在运行时访问generics实例,certificate字节码中确实存在某些东西。

更有意思的一点是(它没有被提出)是使用擦除来实现generics提供了高层次types系统可以完成的更多的灵活性。 一个很好的例子就是Scala的JVM实现和CLR。 在JVM上,由于JVM本身对generics没有任何限制(因为这些“types”实际上是不存在的),因此可以直接实现更高的types。 这与具有参数实例的运行时知识的CLR形成对比。 正因为如此,CLR本身必须对如何使用generics应该有一些概念,从而否定了用未预料的规则来扩展系统的尝试。 因此,Scala在CLR上的更高types是通过在编译器中模拟的一种奇怪的擦除方式来实现的,这使得它们不能完全兼容普通的.NETgenerics。

当你想在运行时做淘气的事情时,擦除可能是不方便的,但它为编译器编写者提供了最大的灵活性。 我猜这是为什么它不会很快消失的一部分。

据我了解(作为一个.NET人) JVM没有generics的概念,所以编译器用Objectreplacetypes参数,并为您执行所有的铸造。

这意味着Javagenerics只不过是语法上的糖,并没有提供任何性能改进的价值types,需要装箱/拆箱通过引用传递。

generics编程是在java 1.5版本中引入的
首先在java中是通用的?
generics编程是一个types安全的对象,在generics之前,我们可以存储任何types的对象。 在通用之后,我们必须存储特定types对象的数据。

通用的优点是什么?
通用的主要优点是不需要types转换,并且type-sage和Generic将检查编译时间。 而Generic的一般语法是ClassOrInterface这里的type是这个类在实例化时可以设置类的信号

例子 。 GenericClassDemo

genericclassDemo = new GenericClassDemo(Employee.java)