为什么有人声称Java的generics实现不好?

我偶尔听说generics,Java没有得到正确的答案。 (最近的参考, 这里 )

请原谅我的经验不足,但是会让他们变得更好吗?

坏:

  • types信息在编译时会丢失,所以在执行的时候,你不能说出它的“意思”是什么types
  • 不能用于值types(这是一个biggie – 在.NET中,一个List<byte>实际上由一个byte[]来支持,并且不需要装箱)
  • 调用generics方法的语法很糟糕(IMO)
  • 约束的语法可能会令人困惑
  • 通配符通常是令人困惑的
  • 上述铸造等各种限制

好:

  • 通配符允许在呼叫方指定协变/逆变,在许多情况下非常整齐
  • 这比没有好!

最大的问题是Javagenerics只是编译时的事情,你可以在运行时颠覆它。 C#被称赞,因为它做更多的运行时检查。 这篇文章有一些非常好的讨论,并链接到其他讨论。

  1. 运行时实现(即不是types擦除);
  2. 使用原始types的能力(这与(1)相关);
  3. 尽pipe通配符是有用的,但语法和知道何时使用它却是很多人困惑的事情。 和
  4. 没有性能改进(因为(1); Javagenerics是语言对象的语法糖)。

(1)导致一些非常奇怪的行为。 我能想到的最好的例子是。 承担:

 public class MyClass<T> { T getStuff() { ... } List<String> getOtherStuff() { ... } } 

然后声明两个variables:

 MyClass<T> m1 = ... MyClass m2 = ... 

现在调用getOtherStuff()

 List<String> list1 = m1.getOtherStuff(); List<String> list2 = m2.getOtherStuff(); 

第二种是它的genericstypes参数由编译器剥离,因为它是一个原始types(意味着参数化types不提供),即使它与参数化types无关

我还会提到我最喜欢的JDK声明:

 public class Enum<T extends Enum<T>> 

除了通配符(这是一个混合包),我只是觉得.Net的generics更好。

主要的问题是Java在运行时实际上并没有generics。 这是一个编译时间function。

当你在Java中创build一个generics类时,他们使用一个名为“Type Erasure”的方法来从类中实际删除所有的genericstypes,并用Object来取代它们。 generics的英里高版本是只要在方法体中出现,编译器就简单地将强制types转换为指定的genericstypes。

这有很多缺点。 恕我直言,最大的一个是,你不能使用reflection来检查generics。 types在字节码中实际上不是通用的,因此不能作为generics来检查。

这里的差异很好的概述: http : //www.jprl.com/Blog/archive/development/2007/Aug-31.html

我要抛出一个非常有争议的观点。 generics使语言复杂化,使代码复杂化。 例如,假设我有一个将string映射到string列表的映射。 在过去,我可以简单地声明为

 Map someMap; 

现在,我必须声明为

 Map<String, List<String>> someMap; 

每次我把它转化成某种方法,我都要重复一遍又一遍的长篇大论。 在我看来,所有额外的打字让开发商分心,并把他从“区域”中带出来。 而且,如果代码中充斥着大量的代码,有时候很难再回到代码中,并迅速地通过所有的代码来find重要的逻辑。

Java已经成为常用的最繁琐的语言之一,而generics只是增加了这个问题。

你真的为什么要买这些冗长的东西? 有人把一个Integer放进一个应该包含Strings的集合中,或者有人试图从一个Integer集合中抽取一个String? 在我从事构build商业Java应用程序的10年经验中,这从来就不是错误的重要来源。 所以,我不确定你会得到什么更多的冗长。 这真的只是把我当作额外的官僚行为。

现在我会变得非常有争议。 我认为Java 1.4中最大的问题是需要在任何地方进行types转换。 我将这些types转换看作是与generics相同的问题。 所以,例如,我不能这样做

 List someList = someMap.get("some key"); 

我要做

 List someList = (List) someMap.get("some key"); 

原因当然是,get()返回一个Listtypes的超类。 所以如果没有一个types转换,这个任务是不可能的。 再次想一想,这条规则真的会给你带来多less收益。 从我的经验来看,并不多。

我认为,如果1)它没有添加generics,而是2)允许隐式强制从超types转换为子types,那么Java会更好。 让不正确的转换在运行时被捕获。 那么我可以有定义的简单

 Map someMap; 

后来在做

 List someList = someMap.get("some key"); 

所有的东西都不见了,我真的不认为我会在代码中引入一个新的bug。

它们是编译时而不是运行时的另一个副作用是你不能调用通用types的构造函数。 所以你不能用它们来实现一个通用的工厂…

 public class MyClass { public T getStuff() { return new T(); } } 

–jeffk ++

忽略整个types擦除混乱,指定的generics只是不起作用。

这编译:

 List<Integer> x = Collections.emptyList(); 

但是这是一个语法错误:

 foo(Collections.emptyList()); 

foo被定义为:

 void foo(List<Integer> x) { /* method body not important */ } 

所以expression式types检查取决于它是否被分配给一个局部variables或一个方法调用的实际参数。 那有多疯狂?

Javagenerics在编译时检查是否正确,然后删​​除所有的types信息(这个过程被称为types擦除 ,因此genericsList<Integer>将被简化为原始types ,非genericsList ,它可以包含任意对象类。

这样可以在运行时将任意对象插入到列表中,而且现在也不可能知道哪些types被用作通用参数。 后者反过来导致

 ArrayList<Integer> li = new ArrayList<Integer>(); ArrayList<Float> lf = new ArrayList<Float>(); if(li.getClass() == lf.getClass()) // evaluates to true System.out.println("Equal"); 

将generics引入到Java中是一件困难的事情,因为架构师试图平衡function,易用性和与遗留代码的向后兼容性。 相当可观的是,必须妥协。

有些人也觉得Java的generics实现将语言的复杂度提高到了难以接受的程度(参见Ken Arnold的“被认为是generics的有害 ”)。 Angelika Langer的generics常见问题解答提供了一个非常好的主意,可以变得多么复杂。

Java不会在运行时强制generics,只能在编译时。

这意味着你可以做一些有趣的事情,比如向generics集合中添加错误的types。

我希望这是一个维基,所以我可以添加到其他人…但…

问题:

  • types擦除(无运行时间可用性)
  • 不支持主要types
  • 与注解不兼容(它们都是在1.5中join的我还不确定为什么注释不允许generics除了冲刷特性)
  • 与数组不兼容。 (有时候我真的很想做类似于Class < ?extends MyObject > []的东西,但我不允许)
  • 奇怪的通配符语法和行为
  • 通用支持在Java类中是不一致的。 他们将其添加到大多数收集方法中,但是偶尔会碰到一个不存在的实例。

Javagenerics只是编译时,并被编译成非generics的代码。 在C#中,实际编译的MSIL是通用的。 这对性能有巨大的影响,因为Java仍然在运行时进行强制转换。 看到这里更多 。

如果您聆听Java Posse#279 – 访问Joe Darcy和Alex Buckley ,他们会谈论这个问题。 这也链接到一个Neal Gafter的博客文章名为Reifiedgenerics为Java说:

很多人对于generics在Java中实现的限制不满意。 具体而言,他们不满意genericstypes参数不具体化:它们在运行时不可用。 generics是使用删除来实现的,在genericstypes参数在运行时被简单地删除。

该博客文章引用了一个较旧的条目,“ 通过擦除困惑:答案部分” ,强调了迁移兼容性的要求。

目标是提供源代码和目标代码的向后兼容性,以及迁移兼容性。