为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

猛跑小猪 2019-11-21 12:49:48
今天,我读了一些有关Java中协方差,协方差(和不变性)的文章。我阅读了英文和德文的Wikipedia文章,以及其他来自IBM的博客文章和文章。但是我对这些到底是什么还是有些困惑?有人说这与类型和子类型之间的关系有关,有人说与类型转换有关,有人说它用于确定方法是被重写还是被重载。因此,我正在寻找一种简单易懂的英语解释,它向初学者展示了协方差和逆方差(和不变性)。加号是一个简单的例子。
查看完整描述

3 回答

?
犯罪嫌疑人X

TA贡献2080条经验 获得超4个赞

有人说这与类型和子类型之间的关系有关,有人说与类型转换有关,还有人说它用于确定方法是被覆盖还是被重载。


上述所有的。


本质上,这些术语描述了类型转换如何影响子类型关系。也就是说,如果A和B是类型,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> { ... } 

是f协变,逆变还是不变?协变意味着a List<String>是的子类型List<Object>,相反,a 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() { ... }

}

我希望我覆盖了所有内容,或者更确切地说,是划伤了表面。我仍然希望它将有助于理解类型差异的抽象但重要的概念。


查看完整回答
反对 回复 2019-11-21
  • 3 回答
  • 0 关注
  • 496 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信