协方差,不变性和逆变性用简单的英语解释?

109 java covariance contravariance

今天,我在Java中阅读了一些关于Covariance,Contravariance(和Invariance)的文章.我阅读了英文和德文维基百科的文章,以及IBM的一些其他博客文章和文章.

但我对这些究竟是什么有点困惑?有人说,它是关于类型和子类型之间的关系,也有人说,它是关于类型转换和一些说,它是用来决定一个方法是否重载或超载.

所以我正在用简单的英语寻找一个简单的解释,它向初学者展示了Covariance和Contravariance(和Invariance).加上一点简单的例子.

mer*_*ike 263

有人说它是关于类型和子类型之间的关系,其他人说它是关于类型转换,而其他人则说它用于决定方法是否被覆盖或过载.

上述所有的.

从本质上讲,这些术语描述了子类型关系如何受到类型转换的影响.也就是说,如果AB是类型,f是一种转型,≤亚型关系(即A ? B意味着A是一个亚型B),我们有

  • f如果A ? B暗示这是协变的f(A) ? f(B)
  • f如果A ? B暗示那是逆变的f(B) ? f(A)
  • f 如果以上都不成立,则不变

让我们考虑一个例子.让我们f(A) = List<A>在哪里List声明

class List<T> { ... } 
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

只会编译typeof(y) ? typeof(x).也就是说,我们刚刚了解到这些陈述

ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();
Run Code Online (Sandbox Code Playgroud)

不会用Java编译,但是

Object[] objects = new String[1];
Run Code Online (Sandbox Code Playgroud)

将.

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

result = method(a);
Run Code Online (Sandbox Code Playgroud)

非正式地说,通过a为方法的第一个参数赋值,然后执行方法体,然后将方法返回值赋值给,来评估此语句result.与上一个示例中的普通赋值一样,"右侧"必须是"左侧"的子类型,即此语句只有在typeof(a) ? typeof(parameter(method))和时才有效returntype(method) ? typeof(result).也就是说,如果方法声明为:

Number[] method(ArrayList<Number> list) { ... }
Run Code Online (Sandbox Code Playgroud)

以下表达式都不会编译:

Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());
Run Code Online (Sandbox Code Playgroud)

Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());
Run Code Online (Sandbox Code Playgroud)

将.

子类型重要的另一个例子是最重要的.考虑:

Super sup = new Sub();
Number n = sup.method(1);
Run Code Online (Sandbox Code Playgroud)

哪里

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

class Sub extends Super {
    @Override 
    Number method(Number n);
}
Run Code Online (Sandbox Code Playgroud)

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

class Super {
    Number method(Number n) {
        if (this instanceof Sub) {
            return ((Sub) this).method(n);  // *
        } else {
            ... 
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

对于要编译的标记行,重写方法的方法参数必须是重写方法的方法参数的超类型,返回类型是重写方法的子类型.从形式上讲,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() { ... }
}
Run Code Online (Sandbox Code Playgroud)

我希望我涵盖了所有内容 - 或者更确切地说,表面上划了一下.我仍然希望它有助于理解类型方差的抽象但重要的概念.

  • 是吗?我只是在eclipse中尝试过,编译器认为我的意思是重载而不是覆盖,并在我在子类方法上放置@Override注释时拒绝了代码.您是否有任何证据表明您支持Java支持逆变参数类型? (12认同)
  • 我阅读了大量文档并观看了一些有关该主题的演讲,但这是迄今为止最好的解释。非常感谢。 (2认同)

Vad*_*imV 16

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

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

协方差

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

如果你问别人“花店在哪里?” 有人告诉你玫瑰店在哪里,可以吗?是的,因为玫瑰是一朵花,如果你想买一朵花,你可以买一朵玫瑰。如果有人回复您菊花店的地址,这同样适用。这是协方差的示例:您可以强制转换A<C>A<B>,其中C是 的子类B,如果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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

static FlowerShop<? extends Flower> tellMeShopAddress() {
    return new RoseShop();
}
Run Code Online (Sandbox Code Playgroud)

逆变

例如,你想送花给你的女朋友。如果你的女朋友喜欢任何一种花,你能把她当作一个爱玫瑰的人,还是一个爱雏菊的人?是的,因为如果她喜欢任何花,她就会喜欢玫瑰和雏菊。这是逆变的一个示例:您可以强制转换A<B>A<C>,其中C是 的子类B,如果使用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!");
    }

}
Run Code Online (Sandbox Code Playgroud)

你把爱任何花的女朋友当成爱玫瑰的人,送她一朵玫瑰:

PrettyGirl<? super Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());
Run Code Online (Sandbox Code Playgroud)

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


Joo*_*gen 12

采用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;
Run Code Online (Sandbox Code Playgroud)

可以看出,:

  • T必须是子类型S(协变量,因为B是A的子类型).
  • V必须是U的超类型(逆变,作为对抗继承方向).

现在,与B是A的子类型共同和相反.可以通过更具体的知识引入以下更强的类型.在子类型中.

协方差(在Java中可用)很有用,可以说在子类型中返回更具体的结果; 尤其是当A = T且B = S时.逆变法说你准备处理一个更普遍的论点.