我听说泛型的 Java 实现不如 C# 实现。在语法看起来很相似的情况下,Java 实现不合标准的地方是什么,或者它是一种宗教观点?
3 回答
streloksi 的链接在消除差异方面做得很好。快速而肮脏的总结是......
在语法和用法方面。语言之间的语法大致相同。这里和那里的一些怪癖(最明显的是在约束中)。但基本上,如果你能读懂一个,你就可以读/用另一个。
最大的区别在于实施。
Java 使用类型擦除的概念来实现泛型。简而言之,底层编译的类实际上并不是通用的。它们编译为 Object 和 casts。实际上,Java 泛型是编译时工件,在运行时很容易被破坏。
另一方面,C# 凭借 CLR 实现了泛型,一直到字节码。为了在 2.0 中支持泛型,CLR 进行了几项重大更改。好处是性能改进、深度类型安全验证和反射。
再次提供的链接有更深入的细分,我鼓励您阅读
差异归结为 Microsoft 和 Sun 的设计决定。
Java中的泛型是由编译器通过类型擦除实现的,这意味着类型检查发生在编译时,类型信息被删除。采用这种方法是为了使遗留代码与使用泛型的新代码兼容:
来自 Java 教程,泛型:类型擦除:
当泛型类型被实例化时,编译器通过一种称为类型擦除的技术来转换这些类型——在这个过程中,编译器会删除与类或方法中的类型参数和类型参数相关的所有信息。类型擦除使使用泛型的 Java 应用程序能够保持与在泛型之前创建的 Java 库和应用程序的二进制兼容性。
但是,对于C# (.NET) 中的泛型,编译器不会擦除类型,并且类型检查是在运行时执行的。这样做的好处是类型信息保留在编译的代码中。
来自维基百科:
这种设计选择用于提供额外的功能,例如允许通过保留泛型类型进行反射,以及减轻擦除的一些限制(例如无法创建泛型数组)。这也意味着运行时强制转换和通常昂贵的装箱转换不会影响性能。
与其说“.NET 泛型优于 Java 泛型”,不如研究实现泛型的方法的差异。在 Java 中,保持兼容性似乎是一个高优先级,而在 .NET(在 2.0 版中引入时),实现使用泛型的全部好处是一个更高的优先级。
还发现与 Anders Hejlsberg的对话也可能很有趣。总结一下 Anders Hejlsberg 用一些额外的注释提出的观点:Java 泛型是为了与现有 JVM 的最大兼容性而设计的,与您在 C# 中看到的实现相比,这导致了一些奇怪的事情:
类型擦除强制实现将每个通用参数化值表示为
Object
.Object
虽然编译器在更具体的类型之间提供自动转换,但它并没有消除类型转换和装箱对性能的负面影响(例如,转换Object
为特定类型MyClass
或int
必须装箱Integer
,这对于 C#/. NET 如果由于用户定义的值类型而遵循类型擦除方法)。正如安德斯所说:“你没有得到任何执行效率”(具体化泛型在 C# 中启用)类型擦除使编译时可用的信息在运行时无法访问。过去的东西
List<Integer>
变成了List
无法在运行时恢复泛型类型参数的东西。这使得围绕 Java 泛型构建反射或动态代码生成场景变得困难。最近的SO 答案通过匿名类显示了一种解决方法。但是如果没有技巧,在运行时通过反射生成代码,从一个集合实例中获取元素并将其放入另一个集合实例,在动态生成的代码执行期间可能会在运行时失败:在这些情况下,反射无助于捕获不List<Double>
匹配List<Integer>
.
但是 +1 链接到 Jonathan Pryor 的博客文章的答案。