今天,我读了一些有关Java中协方差,协方差(和不变性)的文章。我阅读了英文和德文的Wikipedia文章,以及其他来自IBM的博客文章和文章。
但是我仍然对这些到底是什么感到困惑?有人说这与类型和子类型之间的关系有关,有人说与类型转换有关,有人说它用于确定方法是被重写还是被重载。
因此,我正在寻找一个简单的英语解释,它向初学者展示了协方差和逆方差(以及不变性)。加号是一个简单的例子。
今天,我读了一些有关Java中协方差,协方差(和不变性)的文章。我阅读了英文和德文的Wikipedia文章,以及其他来自IBM的博客文章和文章。
但是我仍然对这些到底是什么感到困惑?有人说这与类型和子类型之间的关系有关,有人说与类型转换有关,有人说它用于确定方法是被重写还是被重载。
因此,我正在寻找一个简单的英语解释,它向初学者展示了协方差和逆方差(以及不变性)。加号是一个简单的例子。
Answers:
有人说这与类型和子类型之间的关系有关,有人说与类型转换有关,还有人说它用于确定方法是被覆盖还是被重载。
上述所有的。
从本质上讲,这些术语描述了类型转换如何影响子类型关系。也就是说,如果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() { ... }
}
我希望我覆盖了所有内容,或者更确切地说,是划伤了表面。我仍然希望它将有助于理解类型差异的抽象但重要的概念。
A ≤ B
。这种表示法使事情变得更加简单和有意义。好读书...
以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;
可以看出:
现在,与B是A的子类型相关联并相反。可以使用更具体的知识介绍以下更强的类型。在子类型中。
协方差(在Java中可用)很有用,可以说在子类型中返回了更具体的结果。特别是当A = T和B = S时。矛盾表示您准备处理一个更笼统的论点。
方差是关于具有不同泛型参数的类之间的关系。他们之间的关系是我们可以抛弃他们的原因。
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();
}
}
问题是“花店在哪里?”,答案是“那里的花店”:
static FlowerShop<? extends Flower> tellMeShopAddress() {
return new RoseShop();
}
例如,您想送花给您的女朋友。如果您的女朋友爱上一朵花,您可以将她视为爱玫瑰的人,还是爱雏菊的人?是的,因为如果她喜欢任何花,她都会喜欢玫瑰和雏菊。这是一个例子逆变:你被允许投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!");
}
}
您正在考虑将爱花的女友当作爱玫瑰的人,并给她一朵玫瑰:
PrettyGirl<? super Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());