用简单的英语解释协方差、 Invariance 和反方差?

今天,我读了一些关于 Java 中协变、逆变(和不变)的文章。我阅读了英文和德文的维基百科文章,以及其他一些来自 IBM 的博文和文章。

但我还是有点搞不懂这些到底是什么?有人说它是关于类型和子类型之间的关系,有人说它是关于类型转换,还有人说它是用来决定方法是被重载还是被重载的。

因此,我正在寻找一个简单明了的解释,向初学者展示什么是反变(和不变性)。举个简单的例子。

44764 次浏览

有人说它是关于类型和子类型之间的关系,有人说它是关于类型转换,还有人说它是用来决定一个方法是重写还是重载的。

以上皆有。

实际上,这些术语描述了子类型关系如何受到类型转换的影响。也就是说,如果 AB是类型,f是类型转换,≤亚型关系(即 A ≤ B意味着 AB的亚型) ,我们有

  • 如果 A ≤ B意味着 f(A) ≤ f(B),则 f是协变的
  • 如果 A ≤ B意味着 f(B) ≤ f(A),则 f是逆变的
  • 如果上述两者都不成立,则 f是不变的

让我们考虑一个例子

class List<T> { ... }

f是协变的、逆变的还是不变的?协变将意味着 List<String>List<Object>的亚型,与 List<Object>List<String>的亚型相反,并且两者都不是另一个的亚型,即 List<String>List<Object>是不可转换的类型。在 Java 中,后者是正确的,我们(在一定程度上非正式地)说 非专利药是不变的。

另一个例子。让 f(A) = A[]f是协变的、逆变的还是不变的?也就是说,String []是 Object []的子类型,Object []是 String []的子类型,还是两者都不是另一个的子类型?(答: 在 Java 中,数组是协变的)

这仍然是相当抽象的。为了使它更具体,让我们看看在 Java 中哪些操作是根据子类型关系定义的。最简单的例子是赋值。声明

x = y;

将只编译如果 typeof(y) ≤ typeof(x)。也就是说,我们刚刚学到的语句

ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();

不会在 Java 中编译,但

Object[] objects = new String[1];

威尔。

子类型关系重要的另一个示例是方法调用表达式:

result = method(a);

非正式地说,这个语句的计算方法是将 a的值赋给方法的第一个参数,然后执行方法的主体,然后将方法返回值赋给 result。与上一个例子中的普通赋值一样,“右手边”必须是“左手边”的子类型,也就是说,这个语句只有在 typeof(a) ≤ typeof(parameter(method))returntype(method) ≤ typeof(result)。也就是说,如果声明方法的是:

Number[] method(ArrayList<Number> list) { ... }

下列任何一个表达式都不能编译:

Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());

但是

Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());

威尔。

另一个子类型重要的例子是覆盖。考虑:

Super sup = new Sub();
Number n = sup.method(1);

哪里

class Super {
Number method(Number n) { ... }
}


class Sub extends Super {
@Override
Number method(Number n);
}

非正式地,运行时将把它重写为:

class Super {
Number method(Number n) {
if (this instanceof Sub) {
return ((Sub) this).method(n);  // *
} else {
...
}
}
}

对于要编译的标记行,重写方法的方法参数必须是重写方法的方法参数的超类型,而返回类型是重写方法的子类型。形式上讲,f(A) = parametertype(method asdeclaredin(A))必须至少是逆变的,如果 f(A) = returntype(method asdeclaredin(A))必须至少是协变的。

请注意上面的“至少”。任何合理的静态类型安全的面向对象编程语言都会强制执行这些最低要求,但是编程语言可能会选择更严格的要求。在 Java 1.4的情况下,当重写方法时,参数类型和方法返回类型必须是相同的(除了类型擦除之外) ,也就是说,当重写方法时,parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))必须是相同的。自 Java 1.5以来,协变返回类型在重写时是允许的,也就是说,下面的代码将在 Java 1.5中编译,但不能在 Java 1.4中编译:

class Collection {
Iterator iterator() { ... }
}


class List extends Collection {
@Override
ListIterator iterator() { ... }
}

我希望我涵盖了所有内容——或者更确切地说,只是触及了皮毛。尽管如此,我仍然希望它能帮助理解抽象的,但是重要的类型变化的概念。

使用 java 类型系统,然后分类:

任何类型为 T 的对象都可以替换为类型为 T 的子类型的对象。

类型方差分类方法具有以下结果

class A {
public S f(U u) { ... }
}


class B extends A {
@Override
public T f(V v) { ... }
}


B b = new B();
t = b.f(v);
A a = ...; // Might have type B
s = a.f(u); // and then do V v = u;

可以看出:

  • T 必须是子类型 S (因为 B 是 A 的子类型)。
  • V 必须是 U 的超型(反变量,作为相反的继承方向)。

现在共同和反相关的 B 是 A 的子类型。以下强类型可以引入更具体的知识。在子类型中。

协方差(在 Java 中可用)是有用的,它表示在子类型中返回一个更具体的结果; 特别是当 A = T 和 B = S 时。 矛盾说明你准备好处理一个更普遍的论点。

方差是关于具有不同泛型参数的类之间的关系。它们之间的关系是我们可以强制转换它们的原因。

Co 和 Contra 的差异是非常符合逻辑的。语言类型系统迫使我们支持现实生活中的逻辑。通过例子很容易理解。

协方差

例如,你想买一朵花,你有两个花店在你的城市: 玫瑰商店和雏菊商店。

如果你问某人“花店在哪里?”然后有人告诉你玫瑰花店在哪里,可以吗?是的,因为玫瑰是一种花,如果你想买一朵花,你可以买一朵玫瑰。如果有人回复你菊花店的地址,也是一样。 这是 协方差的一个例子: 如果 A产生泛型值(从函数返回结果) ,那么允许将 A<C>强制转换为 A<B>,其中 CB的一个子类。协方差是关于生产者的。

类型:

class Flower {  }
class Rose extends Flower { }
class Daisy extends Flower { }


interface FlowerShop<T extends Flower> {
T getFlower();
}


class RoseShop implements FlowerShop<Rose> {
@Override
public Rose getFlower() {
return new Rose();
}
}


class DaisyShop implements FlowerShop<Daisy> {
@Override
public Daisy getFlower() {
return new Daisy();
}
}

问题是“花店在哪里?”,答案是“玫瑰花店在那里”:

static FlowerShop<? extends Flower> tellMeShopAddress() {
return new RoseShop();
}

违规行为

比如你想送花给你的女朋友。如果你的女朋友喜欢任何一种花,你会认为她是一个喜欢玫瑰的人,还是一个喜欢雏菊的人?是的,因为如果她喜欢任何一种花,她会同时喜欢玫瑰和雏菊。 这是 违规行为的一个示例: 如果 A使用通用值,则允许将 A<B>强制转换为 A<C>,其中 CB的子类。矛盾是关于消费者的。

类型:

interface PrettyGirl<TFavouriteFlower extends Flower> {
void takeGift(TFavouriteFlower flower);
}


class AnyFlowerLover implements PrettyGirl<Flower> {
@Override
public void takeGift(Flower flower) {
System.out.println("I like all flowers!");
}


}

你把你喜欢任何花的女朋友想象成喜欢玫瑰的人,然后送她一朵玫瑰:

PrettyGirl<? super Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());

你可以在 来源找到更多。