有人说它是关于类型和子类型之间的关系,有人说它是关于类型转换,也有人说它用于决定一个方法是否被覆盖或重载。
以上所有。
本质上,这些术语描述了子类型关系如何受到类型转换的影响。即如果A和B是类型,f是类型转换,≤子类型关系(即A ≤ B表示A是B的子类型),我们有
-
f 是协变的,如果 A ≤ B 暗示 f(A) ≤ f(B)
-
如果
A ≤ B 暗示f(B) ≤ f(A),则f 是逆变的
-
如果以上都不成立,
f 是不变的
让我们考虑一个例子。让f(A) = List<A> 其中List 声明为
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;
只有在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() { ... }
}
我希望我涵盖了所有内容 - 或者更确切地说,只是触及了表面。我仍然希望它有助于理解类型变化这个抽象但重要的概念。