但是我仍然有点困惑这些到底是关于什么?有些人说这是关于类型和子类型之间的关系,有些人说这是关于类型转换,还有一些人说它用于决定方法是覆盖还是重载。
因此,我正在寻找一个简单易懂的英文解释,向初学者展示协变、逆变(和不变性)是什么。如果有简单的例子就更好了。
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;
只有当y
的类型小于等于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
的简洁明了的表示法,我非常赞同并给予加分。这种表示法使得事情变得更加简单和有意义。阅读愉快... - Romeo Sierra方差(Variance)是关于具有不同泛型参数的类之间的关系的。它们之间的关系是我们能够进行类型转换的原因。
协变(Covariance)和逆变(Contra variance)是非常合乎逻辑的事情。语言类型系统强制我们支持现实生活中的逻辑。通过示例很容易理解。
例如,您想购买一朵花,在您的城市里有两家花店:玫瑰花店和雏菊花店。
如果您问某人“花店在哪里?”并且有人告诉您玫瑰花店在哪里,那么可以吗?是的,因为玫瑰是花,如果您想买花,您可以买一朵玫瑰花。如果有人回复您雏菊花店的地址,同样也可以。这是协变的例子:如果A产生泛型值(从函数中作为结果返回),则允许将A<C>强制转换为A<B>,其中C是B的子类。协变是关于生产者的。
类型:
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>
,其中 C
是 B
的子类。 反变关注的是消费者。
类型:
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());
您可以在源中找到更多相关的IT技术内容。
在处理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时。逆变表示您已准备好处理更一般的参数。