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

2022-09-01 07:34:31

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

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


答案 1

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

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

这就是使用站点继承相对于声明站点继承的优势。声明站点继承相对于使用站点继承的优势基本上是用户简洁的(前提是设计人员努力将每个类/接口分离为其协变和逆变部分)。对于类似 或 的东西,最好不要每次使用界面时都指定协方差。Java通过使用冗长的语法使这变得特别烦人(除了Java的解决方案基本上是理想的二方差)。IEnumerableIterator

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

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


答案 2

大多数人似乎更喜欢声明站点方差,因为它使库的用户更容易(同时使库开发人员更难,尽管我认为库开发人员必须考虑方差,而不管方差实际写在哪里。

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

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

C# 的声明站点方差模型减轻了库用户的负担,但在引入已初始化的泛型时,他们基本上将方差规则构建到其 VM 中。即使在今天,由于这个错误,他们也不能完全支持协/逆变(并且重新定义的集合类的非向后兼容引入已将程序员分为两个阵营)。

这对所有面向CLR的语言构成了困难的限制,也是替代编程语言在JVM上更加活跃的原因之一,尽管CLR似乎具有“更好的功能”。

让我们来看看Scala:Scala是一个完全面向对象的函数式混合体,在JVM上运行。它们像Java一样使用类型擦除,但是泛型的实现和(声明站点)方差都比Java(或C#)更容易理解,更直接,更强大,因为VM没有强加关于方差如何工作的规则。Scala编译器检查方差符号,并且可以在编译时拒绝不健全的源代码,而不是在运行时引发异常,而生成的.class文件可以从Java无缝使用。

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

同时,Scala可以使用基元类型,而无需像C#那样将它们装箱在集合中,通过使用注释告诉Scala编译器生成专用于所请求基元类型的类或方法的一个或多个附加实现。@specialized

Scala还可以通过使用清单“几乎”使泛型化,这允许它们在运行时检索泛型类型,就像在C#中一样。


推荐