用简单的英语解释协变、不变和逆变?

新手上路,请多包涵

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

但我对这些到底是什么还是有点困惑?有人说它是关于类型和子类型之间的关系,有人说它是关于类型转换,有人说它用于决定一个方法是否被覆盖或重载。

所以我正在寻找一个简单的英语解释,向初学者展示协变和逆变(和不变)是什么。加点一个简单的例子。

原文由 anon 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 480
2 个回答

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

上述所有的。

从本质上讲,这些术语描述了类型转换如何影响子类型关系。 That is, if A and B are types, f is a type transformation, and ≤ the subtype relation (ie A ≤ B means that AB 的子类型,我们有

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

让我们考虑一个例子。让 f(A) = List<A> 其中 List 由声明

class List<T> { ... }

f 是协变的、逆变的还是不变的? Covariant would mean that a List<String> is a subtype of List<Object> , contravariant that a List<Object> is a subtype of List<String> and invariant that neither is另一个的子类型,即 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() { ... }
}

我希望我涵盖了所有内容——或者更确切地说,只触及了表面。我仍然希望它有助于理解抽象但重要的类型变化概念。

原文由 meriton 发布,翻译遵循 CC BY-SA 4.0 许可协议

差异是关于具有不同泛型参数的类之间的关系。他们的关系是我们可以施放他们的原因。

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

协方差

例如,您想买一朵花,而您所在的城市有两家花店:玫瑰店和雏菊店。

如果你问某人“花店在哪里?”有人告诉你玫瑰店在哪里,可以吗?是的,因为玫瑰是一种花,如果你想买一朵花,你可以买一朵玫瑰。如果有人用雏菊店的地址回复你,这同样适用。 This is example of covariance : you are allowed to cast A<C> to A<B> , where C is a subclass of B , if A 生成通用值(作为函数的结果返回)。协方差与生产者有关。

类型:

 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();
}

逆变

例如你想送花给你的女朋友。如果你的女朋友喜欢什么花,你能把她看成是爱玫瑰的人,还是爱雏菊的人?是的,因为如果她喜欢任何一朵花,她会同时喜欢玫瑰和雏菊。 This is an example of the contravariance : you’re allowed to cast A<B> to A<C> , where C is subclass of B , if A 消耗通用值。逆变是关于消费者的。

类型:

 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());

您可以在 Source 找到更多信息。

原文由 VadzimV 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题