Java中的不变性、协变性和逆变性

7

Java 泛型课程让我了解到协变和逆变概念。这让我感到头痛,因为我找不到一个非常简单的演示它是什么的方法。

我已经阅读了几个stackoverflow上类似的问题,但我发现它们对于一个Java学习者来说太难理解了。实际上,泛型的解释需要理解协变和逆变,而协变和逆变的概念则依赖于对泛型的理解进行演示。

我曾经有一些希望在这里阅读,但最后我和C. R.有同样的感觉:

这个标题让我想起了学习广义相对论的日子。 - C.R. Dec 22 '13 at 7:34

有四个理论问题让我感到非常困惑,我找不到好的简单解释。以下是它们,以及我目前的部分理解(我担心专家们会对此感到很有趣)。

欢迎您帮助我纠正和澄清(请记住这是为初学者而不是专家准备的)。

这种理解是否存在问题?

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

2
为什么这个问题太宽泛了?这是关于方差的,应该理解为:什么是方差?同时避免类似于我之前在这个网站上找到的部分答案。目标是为这个主题建立一个参考。你建议如何缩小范围? - mins
尝试在一个线程中询问协变性的示例,在另一个线程中询问逆变性,在另一个线程中询问不变性,也许潜在的答案会足够简短,以至于这些问题将保持开放状态。 - Platinum Azure
谢谢,虽然我不喜欢这个想法,因为学习者更喜欢完整和一致的答案,至少这是我在寻找的。如果没有其他办法,我会这样做。 - mins
1个回答

8
这与面向对象编程无关,但与某些类型的属性有关。例如,对于函数类型:
 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的列表,因为如果是这样的话,我们可以在Cs的列表中存储A,这会让调用者感到惊讶,因为他们只假设列表中有Cs。但是,在值是不可变的语言(如Haskell)中,这不是问题。因为我们传递给函数的数据无法改变,所以如果C是A的子类型,那么C的列表实际上就 A的列表。(请注意,Haskell没有真正的子类型,而是具有相关的“更/少泛型”类型的概念。)

1
谢谢,这篇文章让我明白了泛型方法中的类型参数是不变的,并且在非泛型方法中并没有什么新东西,只是术语不同。我认为在“函数的返回类型是逆变的,而方法的参数类型是协变的”这句话中,你颠倒了方差的概念。如果需要的话,请更正一下,这样我就可以选择您的答案作为“被接受的答案”,或者请告诉我我是否理解有误。 - mins
@Approachingminimums 没错,你看,人们很容易混淆并弄错它,这就是我不使用这个术语的原因。 - Ingo
1
@ingo 谢谢,你的解释更易于理解。 - user3103957

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接