C#与Java泛型的区别

134

我听说Java实现的泛型不如C#实现得好。虽然语法看起来类似,但是Java实现有哪些不足之处?这是一种宗教观点吗?


1
这里有一份全面的比较列表(链接在此:http://www.jprl.com/Blog/archive/development/2007/Aug-31.html),附带一些相关链接。 - Strelok
看起来这个问题已经被问过了 - https://dev59.com/C3VD5IYBdhLWcg3wRpiX - serg10
3个回答

183

streloksi的链接做了很好的细分差异。 简单地说...

就语法和用法而言,两种语言的语法基本相同,但还存在一些小问题(尤其是在约束方面)。但基本上,如果你能阅读其中一种,你可能也能阅读/使用另一种。

最大的区别在于实现方式。

Java使用类型擦除的概念来实现泛型。简而言之,底层编译类实际上并不是通用的。它们会编译成Object和转换。实际上,Java泛型是编译时的产物,并且可以很容易地在运行时被破坏。

另一方面,由于CLR的优势,C#将泛型实现到字节码层面。CLR在2.0中进行了多次重大变革以支持泛型。优点是性能提高、深度类型安全验证和反射。

再次提供的链接有更详细的细分,我鼓励你去阅读。


1
我已经阅读了您提供的Strelok链接、Anders Hejlsberg的采访文章以及Eric Lippert的博客文章。但我仍然不明白——为什么C#不进行类型擦除,而引用类型又共享相同的IL代码?您能否稍微详细解释一下? - Alexander Malakhov
@AlexanderMalakhov 所有引用类型在汇编中都是一个字长,并且可以使用相同的指令进行操作,因此它们可以共享相同的汇编模式。您希望它们有何不同之处? - JaredPar
我猜Java做的事情也是一样的。那么,类型信息在哪里?例如,反射如何工作? - Alexander Malakhov
3
@AlexanderMalakhov 每个实例仍然有一个唯一的类型对象,只是方法槽都指向同一个实现。 - JaredPar
看起来它将会被更改为Java 10 :) - Ced
1
没有Java 10的计划。不知道它的性能提升了多少! - Dragonborn

39

这种差异归结为Microsoft和Sun公司的设计决策。

Java中的泛型是通过编译器的类型擦除实现的,这意味着类型检查发生在编译时,并且类型信息被移除。采用这种方法是为了使遗留代码与使用泛型的新代码兼容:

来自Java教程的泛型:类型擦除:

当一个泛型类型被实例化时, 编译器通过一种称为类型擦除的技术来翻译这些类型——这是一种过程,在这个过程中,编译器删除类或方法中与类型参数和类型参数相关的所有信息。类型擦除使使用泛型的Java应用程序能够与创建于泛型之前的Java库和应用程序保持二进制兼容性。

然而,在C#(.NET)的泛型中,编译器没有类型擦除,类型检查是在运行时执行的。这带来了它的好处,即类型信息被保留在编译后的代码中。

来自维基百科:

这种设计选择被利用来提供附加功能,例如允许反射保留泛型类型,以及缓解一些类型擦除的限制(例如无法创建泛型数组)。这也意味着不存在运行时转换和通常昂贵的装箱转换所带来的性能损失。

与其说“ .NET泛型比Java泛型更好”,我们应该关注实现泛型的方法上的区别。在Java中,保持兼容性似乎是一个很高的优先级,而在.NET中(在2.0版本引入时),实现使用泛型的全部好处是更高的优先级。


@Tom,“Immunity from analysis”是什么意思? - makerofthings7
17
可以这么说,“.NET 泛型比 Java 泛型好”。Java 在有机会进行重大改变并正确实现泛型的时候,应该采取这种方法。请注意,这仅是翻译,不包括任何解释或其他内容。 - Mike
3
“向后兼容性”是不正确的术语,它们两者都是向后兼容的。这是为了“迁移兼容性”而进行的。 - kervin
@kervin 希望您能详细说明一下。 - Vahid Hashemi

12

还发现了这个与安德斯·海尔斯伯格的对话,可能也很有趣。总结一下安德斯·海尔斯伯格提出的一些观点以及一些额外的说明:Java泛型是为了与现有JVM最大程度的兼容而设计的,这导致了一些与C#中实现相比的奇怪之处:

  • 类型擦除强制实现将每个泛型参数化值都表示为Object。虽然编译器提供了Object和更具体类型之间的自动转换,但它不能消除类型转换和装箱对性能的负面影响(例如,将Object转换为特定类型MyClassint必须被封装在Integer中,在类型擦除方法中,如果遵循用户定义值类型,则对于C#/.NET来说这将更加严重)。正如安德斯所说:“你不会获得任何执行效率”(这是C#中使泛型可反射的方式)。

  • 类型擦除使得在运行时不可访问在编译时可用的信息。曾经被定义为List<Integer>的东西变成了一个只有List的东西,在运行时无法恢复泛型类型参数。 这使得在Java泛型中难以构建反射或动态代码生成场景。更近期的SO答案通过匿名类展示了一种解决方法。但是没有技巧,像通过反射在运行时生成代码,从一个集合实例获取元素并将其放入另一个集合实例的情况可能会在动态生成的代码执行期间在运行时失败:在这些情况下,反射无法帮助捕捉到List<Double>List<Integer>之间的不匹配。

但要点赞答案链接到乔纳森·普莱尔的博客文章


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接