关于泛型:Java中的不变性,协方差和逆变性

Invariance, covariance and contravariance in Java

泛泛型的Java课程引导我学习差异概念。这让我有些头疼,因为我找不到一个很简单的演示。

我在StAcExoad上读了几个类似的问题,但是我发现对于Java学习者来说,它们太难理解了。事实上,问题在于,对泛型的解释需要理解方差,而方差的概念在很大程度上依赖于泛型的理解。

读到这篇文章我有点希望,但最后我还是分享了C.R.的感觉:

The title reminds me of the days learning general relativity. – C.R.
Dec 22 '13 at 7:34

四个理论问题让我很困惑,我找不到好的简单的解释。他们在这里,以我目前的部分理解(我担心专家会有一个很大的乐趣阅读这)。

欢迎您帮助纠正和澄清(记住这是给初学者的,不是给专家的)。

这种理解有什么问题吗?

  • 在编程环境中,不变性/协方差/反方差与什么相关?我的最佳猜测是:
    • 这是面向对象编程中遇到的问题。
    • 当查看类和祖先中的方法参数和结果类型时,必须这样做。
    • 这在方法重写和重载的上下文中使用。
    • 这用于在方法参数的类型或方法返回类型与类本身的继承之间建立连接,例如,如果类D是类A的后代,对于参数类型和方法方法返回类型,我们可以说什么?
  • 方差与Java方法有何关系?我的最佳猜测是,给定两个类A和D,其中a是d的祖先,而overhiden/overloaded方法f(arg):
    • 如果两个方法中的参数类型之间的关系与两个类之间的关系相同,则该方法中的参数类型称为与类类型协变,否则称:A和D中的arg类型之间的继承与类A和D的继承协变。
    • 如果参数之间的关系与类之间的关系相反,则称arg类型与类类型相反,否则称:a和d中arg类型之间的继承与a和d类的继承相反。
  • 为什么对Java程序员来说,差异理解如此重要?我的猜测是:
    • Java语言的创建者已经在语言中实现了差异的规则,这对程序员可以做什么有影响。
    • 规则规定重写/重载方法的返回类型必须与继承相反。
    • 另一个规则规定,重写/重载的参数类型必须与继承协变。
    • Java编译器检查方差规则是有效的,并相应地提供错误或警告。使用方差知识更容易破译消息。
  • 过载和过载有什么区别?最佳猜测:
    • 当参数和返回类型都不变时,方法重写另一个方法。编译器将所有其他情况理解为重载。

  • 这不是OO特有的,但与某些类型的属性有关。

    例如,使用函数类型

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

    我们有以下内容:

    设c为a的子类型,d为b的子类型,则以下内容有效:

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

    但以下内容无效:

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

    因此,为了检查一个调用冰毒是否有效,我们必须检查

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

    这是众所周知的和直观的。通常我们说函数的返回类型是协变的,方法的参数类型是反变的。

    通过参数化类型(如列表),我们认为,在Java这样的语言中,参数类型是不变的,在这里我们具有可变性。我们不能说C的列表是A的列表,因为如果是的话,我们可以将A存储在C的列表中,这让调用者很惊讶,他们只假设列表中的C。但是,在值不可变的语言中,比如haskell,这不是问题。因为我们传递给函数的数据不能突变,所以C的列表实际上是if C的子类型的列表(注意haskell没有真正的子类型,而是有"更多/更少多态"类型的相关概念)。