【问题标题】:Invariance, covariance and contravariance in JavaJava中的不变性、协变和逆变
【发布时间】:2014-03-06 00:24:50
【问题描述】:

Java lesson on generics 将我引向 variance 概念。这让我有些头疼,因为我找不到一个非常简单的演示。

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

看了this,还是有些希望的,但最后还是分享了@98​​7654325@的感受:

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

四个理论问题让我很困惑,我找不到好的和简单的解释。就我目前的部分理解来说,就是这些了(我担心专家们阅读这篇文章会很有趣)。

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

这种理解有问题吗?

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

【问题讨论】:

  • 为什么这个问题太宽泛了?这是关于方差的,可以理解为:究竟什么是方差?同时避免与我之前在本网站上找到的类似的部分答案。目标是为此主题设置参考。你建议如何缩小范围?
  • 尝试询问一个线程中的协变、另一个线程中的逆变以及另一个线程中的不变性的示例,可能潜在的答案会足够短,以至于问题将保持开放。
  • 谢谢,虽然我不喜欢这个想法,因为学习者希望得到一个完整且一致的答案,至少这是我正在寻找的。如果没有其他办法,我会这样做。

标签: java generics inheritance covariance contravariance


【解决方案1】:

这不是特定于 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

因此,要检查对 meth 的调用是否有效,我们必须检查

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

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

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

【讨论】:

  • 谢谢。这有助于我理解泛型方法中的类型参数是不变的,并且在非泛型中没有什么新东西,只是新词汇的问题。我认为您在“函数的返回类型是逆变的,而方法的参数类型是协变的”中反转了方差。如有必要,请更正,以便我将您的答案选为“已接受”,或者如果我错了,请告诉我。
  • @Approachingminimums 是的,你看,很容易混淆并弄错,这就是我自己不使用这个术语的原因。
  • @ingo 谢谢,你的解释更容易理解。
猜你喜欢
  • 2012-12-03
  • 2015-02-09
  • 2012-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-03
  • 1970-01-01
  • 2015-01-16
相关资源
最近更新 更多