Java中的不变性,协方差和逆变性

时间:2014-02-08 14:33:21

标签: java generics inheritance covariance contravariance

Java lesson on generics引导我variance概念。这让我有些头疼,因为我无法找到它的简单演示。

我已经阅读了几个similar questions on stackoverflow,但我发现它们对Java学习者来说太难理解了。实际上问题在于对泛型的解释需要理解方差,并且证明了方差的概念在很大程度上依赖于泛型理解。

我有一些希望阅读this,但最后我分享了C. R.的感受:

  

这个标题让我想起了学习广义相对论的日子。 - C.R.   2013年12月22日7:34

四个理论问题对我来说非常困惑,我找不到好的和简单的解释。在这里,他们是我目前的部分理解(我担心专家会非常有乐趣阅读这篇文章)。

欢迎您提供纠正和澄清的帮助(请记住,这适用于初学者,而非专家)。

这种理解有问题吗?

  1. 什么是与编程相关的不变性/协方差/逆变?我最好的猜测是:
    • 这是面向对象编程中遇到的问题。
    • 在查看类和祖先中的方法参数和结果类型时,必须这样做。
    • 这用于方法overridingoverloading
    • 的上下文中
    • 这用于在方法参数的类型或方法返回类型与类本身的继承之间建立连接,例如: 如果D类是A类的后代,我们可以对参数类型和方法方法返回类型说些什么呢?
  2. 方差如何与Java方法相关?我最好的猜测是,给定两个A和D类,其中A是D的祖先,以及overhiden / overloaded方法f(arg):
    • 如果两个方法中的参数类型之间的关系比两个类之间的关系相同,则方法中的参数类型称为COVARIANT,具有类型,否则表示:A中的arg类型之间的继承D与A和D类的继承协变。
    • 如果参数之间的关系REVERSES类之间的关系,则arg类型表示对类类型的CONTRAVARIANT,否则说:A和D中arg类型之间的继承与A类和D类的继承相反。
  3. 为什么方差理解对Java程序员如此重要?我的猜测是:
    • Java语言创建者已经实现了语言中的差异规则,这对程序员可以做的事情有影响。
    • 规则规定覆盖/重载方法的返回类型必须与继承相反。
    • 另一条规则规定,覆盖/重载的参数类型必须与继承协变。
    • Java编译器检查方差规则是否有效,并相应地提供错误或警告。使用差异知识可以更轻松地解密消息。
  4. 过度训练和超载之间有什么区别?最佳猜测:
    • 当参数和返回类型都是不变的时,方法会覆盖另一个方法。编译器将所有其他情况理解为重载。

1 个答案:

答案 0 :(得分:4)

这不是特定于OO,而是与某些类型的属性有关。

例如,使用函数类型

 A -> B                 // functional notation
 public B meth(A arg)   // how this looks in Java 

我们有以下内容:

设C为A的子类型,D为B的子类型。以下是有效的:

 B b       = meth(new C());  // B >= B, C < A
 Object o  = meth(new C());  // Object > B, C < A

但下面的内容无效:

 D d       = meth(new A());        // because D < B
 B b       = meth(new Object());   // because Object > A

因此,要检查甲基化的调用是否有效,我们必须检查

  • 预期的返回类型是声明的返回类型的超类型
  • 实际参数类型是声明的参数类型的子类型

这一点众所周知且直观。按照惯例,我们说函数的返回类型是协变,方法的参数类型是逆变

使用参数化类型(如List),我们知道参数类型在Java等语言中是不变的,我们在这些语言中具有可变性。我们不能说C的列表是A的列表,因为如果它是这样的话,我们可以在C列表中存储A,这对于调用者来说是令人惊讶的,他们假设列表中只有Cs。但是,在值不可变的语言中,如Haskell,这不是问题。因为我们传递给函数的数据不能被改变,所以如果C 是A的子类型,则实际的列表是列表。(注意,Haskell没有真正的subtyping,而是相关的“更多/更少多态”类型的概念。)