Java的使用站点差异与C#的声明站点差异相比如何?

时间:2010-11-20 04:56:42

标签: c# java generics variance

我的理解是在C#中为泛型指定方差发生在类型声明级别:当您创建泛型类型时,指定类型参数的方差。另一方面,在Java中,指定了使用泛型的方差:当您创建某个泛型类型的变量时,您可以指定其类型参数的变化方式。

每个选项的优缺点是什么?

4 个答案:

答案 0 :(得分:38)

我将回答声明站点和使用站点差异之间的差异,因为虽然C#和Java泛型在许多其他方面有所不同,但这些差异大多与方差正交。

首先,如果我没记错的话,使用站点方差比声明站点方差更强大(尽管以简洁为代价),或者至少是Java的通配符(实际上比使用站点方差更强大) 。这种增强的功能对于大量使用有状态构造的语言特别有用,例如C#和Java(但Scala要少得多,特别是因为它的标准列表是不可变的)。考虑List<E>(或IList<E>)。由于它具有添加E和获得E的方法,因此它对于E是不变的,因此不能使用声明站点方差。但是,使用网站差异,您只需说List<+Number>即可获得ListList<-Number>的协变子集,以获得List的逆变子集。在声明站点语言中,库的设计者必须为每个子集创建单独的接口(或类,如果允许多个类的继承),并List扩展这些接口。如果图书馆设计师没有这样做(请注意C#的IEnumerable仅执行IList的协变部分的一小部分),那么你运气不好,你必须采取同样的麻烦你必须用一种没有任何差异的语言来做。

这就是使用站点继承优于声明站点继承的优点。声明站点继承优于使用站点继承的优点对于用户来说基本上是简洁的(前提是设计者经历了将每个类/接口分成其协变和逆变部分的努力)。对于像IEnumerableIterator这样的东西,每次使用界面时都不必指定协方差。 Java使用冗长的语法使得这一点特别烦人(除了对于Java解决方案基本理想的双重性)。

当然,这两种语言功能可以共存。对于自然协变或逆变的类型参数(例如在IEnumerable / Iterator中),请在声明中声明。对于自然不变的类型参数(例如在(I)List中),请在每次使用时声明所需的方差。只是不要为具有声明 - 站点差异的参数指定使用站点方差,因为这会让事情变得混乱。

还有其他更详细的问题我没有涉及(比如通配符实际上比使用网站差异更强大),但我希望这能回答你的问题。我承认我偏向于使用网站差异,但我试图描述我与程序员和语言研究人员讨论时出现的两个主要优点。

答案 1 :(得分:16)

大多数人似乎更喜欢声明网站差异,因为它使图书馆的用户更容易(虽然对图书馆开发人员来说有点困难,尽管我认为图书馆开发人员必须考虑方差,无论实际写入方差的位置。)

但请记住,Java和C#都不是良好语言设计的例子。

虽然 Java 由于Java 5和类型擦除中的兼容VM改进而使得JVM独立于JVM并且独立工作,但是使用站点方差使得使用有点麻烦且类型的特定实现-erasure引起了当之无愧的批评。

C#的声明 - 站点差异模型减轻了库的用户的负担,但在引入定义的泛型时,他们基本上将差异规则构建到他们的VM中。 即使在今天,他们也不能完全支持共同/逆变,因为这个错误(并且非反向兼容的引入已经将程序员分成两个阵营)。

这对所有针对CLR的语言都提出了一个严格的限制,这也是为什么替代编程语言在JVM上更加生动的原因之一,尽管CLR似乎具有“更好的功能”。

让我们看一下 Scala :Scala是一个在JVM上运行的完全面向对象的功能混合体。 他们使用像Java这样的类型擦除,但是泛型和(声明 - 站点)方差的实现比Java(或C#)更容易理解,更直接和更强大,因为VM没有对方差如何规定工作。 Scala编译器检查方差符号,​​并且可以在编译时拒绝不正确的源代码 而不是在运行时抛出异常,而生成的.class文件可以从Java无缝地使用。

声明网站差异的一个缺点是它似乎在某些情况下使类型推断更难。

同时,Scala可以使用原始类型,而无需在C#中使用@specialized注释将它们装入集合中,该注释告诉Scala编译器生成一个或多个专门用于请求的类或方法的其他实现原始类型。

Scala也可以通过使用Manifest“几乎”来统一泛型,它允许它们在运行时检索泛型类型,就像在C#中一样。

答案 2 :(得分:1)

Java样式泛型的缺点

一个结果是java版本仅适用于引用类型(或盒装值类型)而不适用于值类型。 IMO是最大的缺点,因为它在很多场景中都会阻止高性能泛型,并且需要手动编写专门的类型。

它不保证像“此列表仅包含x类型的对象”这样的不变量,并且需要在每个getter处进行运行时检查。泛型类型确实存在。

使用反射时,您无法询问通用对象的实例,它具有哪些通用参数。

Java样式泛型的优点

你可以在不同的通用参数之间获得差异/推理。

答案 3 :(得分:0)

Java:自Java 5以来的使用站点方差泛型。从1.0开始,使用不同语法破坏协变数组。没有泛型的运行时类型信息。

C#:自C#2.0以来的使用站点差异泛型。在C#4.0中添加了声明站点差异。自1.0以来具有不同语法的破坏的协变数组(与Java相同的问题)。 “reified”泛型意味着类型信息在编译时不会丢失。

Scala:自该语言的早期版本(至少从2008年开始)以来,使用站点/声明站点差异。数组不是单独的语言功能,因此您使用相同的泛型语法和类型方差规则。某些集合在VM级别使用JVM阵列实现,因此与Java代码相比,您可以获得相同或更好的运行时性能。

详细说明C#/ Java数组类型安全问题:您可以将Dog []强制转换为Pet []并添加Cat并触发在编译时未捕获的运行时错误。 Scala正确实现了这一点。