为什么有人声称Java的generics实现不好?
我偶尔听说generics,Java没有得到正确的答案。 (最近的参考, 这里 )
请原谅我的经验不足,但是会让他们变得更好吗?
坏:
- types信息在编译时会丢失,所以在执行的时候,你不能说出它的“意思”是什么types
- 不能用于值types(这是一个biggie – 在.NET中,一个
List<byte>
实际上由一个byte[]
来支持,并且不需要装箱) - 调用generics方法的语法很糟糕(IMO)
- 约束的语法可能会令人困惑
- 通配符通常是令人困惑的
- 上述铸造等各种限制
好:
- 通配符允许在呼叫方指定协变/逆变,在许多情况下非常整齐
- 这比没有好!
最大的问题是Javagenerics只是编译时的事情,你可以在运行时颠覆它。 C#被称赞,因为它做更多的运行时检查。 这篇文章有一些非常好的讨论,并链接到其他讨论。
- 运行时实现(即不是types擦除);
- 使用原始types的能力(这与(1)相关);
- 尽pipe通配符是有用的,但语法和知道何时使用它却是很多人困惑的事情。 和
- 没有性能改进(因为(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参数在运行时被简单地删除。
该博客文章引用了一个较旧的条目,“ 通过擦除困惑:答案部分” ,强调了迁移兼容性的要求。
目标是提供源代码和目标代码的向后兼容性,以及迁移兼容性。