什么是泛型泛型?它们如何解决“类型擦除”问题?为什么不进行重大更改就不能添加它们?


73

我已经阅读了尼尔·古夫特(Neal Gafter)关于该主题博客,但在很多方面仍不清楚。

为什么在给定Java,JVM和现有collection API当前状态的情况下,无法创建Collections API的实现来保留类型信息?这些难道不能以保留向后兼容性的方式替换Java未来版本中的现有实现吗?

举个例子:

List<T> list = REIList<T>(T.Class);

REIList是这样的:

public REIList<T>() implements List {
  private Object o;
  private Class klass;

  public REIList(Object o) {
    this.o = o;
    klass = o.getClass();
  }
... the rest of the list implementation ...

并且这些方法使用Object o和Class klass来获取类型信息。

为什么保留通用类信息需要更改语言而不仅仅是JVM实现更改?

我不明白什么?

Answers:


40

重点是,在编译器中,统一化泛型支持保留类型信息,而类型擦除泛型则不支持。AFAIK首先具有类型擦除的全部目的是为了实现向后兼容性(例如,较低版本的JVM仍可以理解通用类)。

您可以像上面一样在实现中显式添加类型信息,但是每次使用列表时都需要附加代码,我认为这很混乱。同样,在这种情况下,除非您自己添加检查,否则仍然没有对所有列表方法进行运行时类型检查,但是经过修饰的泛型将确保运行时类型。


5
它是为较早的JVM编写的源代码和目标代码,可以在新的JVM上运行而无需进行任何更改(源代码将使用“枚举”作为标识符有问题,但不是通用的)。
汤姆·哈特芬

1
“较低版本的JVM仍然可以理解通用类” <-我认为当您尝试在较旧版本的JRE / JVM中运行使用较新版本的JDK编译的程序时,会收到UnsupportedClassVersionError。因此,事实应该就是汤姆·豪顿和理查德·戈麦斯所说的,而事实恰恰相反。
blackr1234 '18

19

与大多数Java开发人员的看法相反,尽管存在非常严格的限制,但仍可以保留编译时类型信息并在运行时检索此信息。换句话说:Java确实以非常受限的方式提供了泛型泛型

关于类型擦除

请注意,在编译时,编译器具有可用的完整类型信息,但是生成二进制代码时,通常会在称为类型擦除的过程中有意删除此信息。由于存在兼容性问题,因此采用了这种方式:语言设计人员的意图是在平台版本之间提供完全的源代码兼容性和完全的二进制代码兼容性。如果实施方式不同,则在迁移到平台的较新版本时,您将不得不重新编译旧版应用程序。这样做的方式将保留所有方法签名(源代码兼容性),而您无需重新编译任何内容(二进制兼容性)。

关于Java中的泛型泛型

如果需要保留编译时类型信息,则需要使用匿名类。关键是:在匿名类的非常特殊的情况下,可以在运行时检索完整的编译时类型信息,换句话说就是:泛型泛型。

我写了一篇关于这个主题的文章:

http://rgomes-info.blogspot.co.uk/2013/12/using-typetokens-to-retrieve-generic.html

在本文中,我描述了用户对这项技术的反应。简而言之,这是一个晦涩的主题,对于大多数Java开发人员来说,该技术(或模式,如果您愿意的话)看起来都是多余的。

样例代码

我上面提到的文章中包含指向实现该思想的源代码的链接。


10
这与泛型泛型无关。是的,类声明中的泛型(如泛型参数中的类型,字段类型,方法签名中的类型,超类类型,内部类的封闭类类型(匿名类没有什么特殊之处))存储在字节码中,因为其他类使用此类并且仅具有.class文件的应用程序需要知道这些内容以实施泛型。但是,关于固定和非固定泛型的讨论都讨论了运行时对象的泛型类型,它与您在上面所说的无关。
newacct 2012年

1
@newacct:是的,确实如此。是的,从内部的角度来看,内部类是特殊的,它们保留可在运行时检索的编译时信息。根据定义,您可以在运行时从泛型类中检索编译时类型信息...您拥有什么?
理查德·戈麦斯

6
您还可以同等地检索有关ANY类的超类,超接口,字段和方法等的编译时信息。
newacct 2012年

10
不,您只是在检索用于扩展其超类的泛型类型参数。匿名类创建表达式创建类的实例,该类是您提供的类型的子类。final K k1 = new K<java.lang.Double>() {};与本地课程完全相同class Foo extends K<java.lang.Double> {}; final K k1 = new Foo();
newacct 2012年

2
这至少不是问题所在。这就是为什么OP描述的统一化实现需要语言更改而不是JVM更改。您的答案都不是。
user207421'7

14

在IIRC(基于链接)上,Java泛型只是使用Object集合和来回转换的现有技术的语法糖。使用Java泛型更加安全和简单,因为编译器可以为您进行检查,以验证您保持编译时类型的安全性。但是,运行时间是一个完全不同的问题。

另一方面,.NET泛型实际上创建了新类型List<String>-C#中的a与a是不同的类型List<Int>。在Java中,它们是相同的东西-a List<Object>。这意味着,如果每个都有,那么您将无法在运行时查看它们并看到它们被声明为什么-只有它们现在是什么。

经过改进的泛型将改变这一点,从而为Java开发人员提供与.NET现在相同的功能。


2
我将您的List <*>放在反引号中,以便显示类型。
David Z

@David-谢谢。我为别人做过这么多次,您认为我会记得……
哈珀·谢尔比

您可以使用匿名类来模仿某种类型的泛型泛型,即:在这种情况下,类型信息在运行时可用。尽管可以做到,但它的适用性非常有限,而且技术非常棘手。Regeneric Generics是一个大小姐。确实是这样。
理查德·戈麦斯

4

我不是该主题的专家,但是据我了解,类型信息在编译时会丢失。与C ++不同,Java不使用模板系统,类型安全完全通过编译器实现。在运行时,列表实际上始终是列表。

因此,我的看法是,由于类型信息由于不存在而无法用于JVM ,因此需要更改语言规范。


在大多数情况下都是如此,除非您有匿名类。我的意思是:当您使用匿名类时,可以避免类型擦除。
理查德·戈麦斯

要更具体地说明该主题:Java不会像C ++那样为模板生成代码的多个副本。Java在使用对象的被调用者站点上生成单个副本。因此,尽管在匿名类的情况下可以使用运行时类型信息,但仅此而已:信息本身。字节码不针对您将在运行时传递的实际类型。
理查德·戈麦斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.