首先,让我们通常理解一下子类型规则是什么。
协变vs逆变vs双变vs不变
编程语言可能有支持以下子类型规则的特性:
协变
允许用超类型替换子类型。
逆变
允许用子类型替换超类型。
双变
同时是协变和逆变。
不变
不允许上述任何替换。
让我们看看Java支持哪些子类型规则:
Java数组是协变的
Java数组支持协变替换:
Integer[] integers = new Integer[10];
Number[] numbers = integers;
但是,上面的赋值是危险的,因为它可能最终导致ArrayStoreException:
public class CovariantArraysExample {
public static void main(String[] args) {
Integer[] integers = new Integer[10];
Number[] numbers = integers;
numbers[0] = new Double(25);
}
}
输出
Caused by: java.lang.ArrayStoreException: java.lang.Double
at com.logicbig.example.CovariantArraysExample.main(CovariantArraysExample.java:8)
... 6 more
Java泛型是不变的
Java泛型具有特定类型T,不支持协变或逆变替换。这是为了避免像我们在上面的数组案例中看到的那样出现ArrayStoreException的情况。
public class InvariantGenericsExample {
public static void main(String[] args) {
List<Integer> integers = new ArrayList<>();
List<Number> numbers = new ArrayList<>();
numbers = integers;
}
}
输出
Compilation errors: InvariantGenericsExample.java:[11,19] incompatible types: java.util.List<java.lang.Integer> cannot be converted to java.util.List<java.lang.Number> 1 error
带有通配符'?extends'的Java泛型是协变的:
public class CovariantGenericsExample {
public static void main(String[] args) {
List<Integer> integers = new ArrayList<>();
integers.add(1);
List<? extends Number> numbers = integers;
System.out.println(numbers);
}
}
输出
[1]
在赋值给List<? extends Number>后,我们不能向List中添加Number的任何子类型。例如,这样的尝试将导致编译错误:
public class CovariantGenericsExample2 {
public static void main(String[] args) {
List<Integer> integers = new ArrayList<>();
integers.add(1);
List<? extends Number> numbers = integers;
Double d = new Double(23);
numbers.add(d);
}
}
输出
Compilation errors: CovariantGenericsExample2.java:[14,16] no suitable method found for add(java.lang.Double) method java.util.Collection.add(capture#1 of ? extends java.lang.Number) is not applicable (argument mismatch; java.lang.Double cannot be converted to capture#1 of ? extends java.lang.Number) method java.util.List.add(capture#1 of ? extends java.lang.Number) is not applicable (argument mismatch; java.lang.Double cannot be converted to capture#1 of ? extends java.lang.Number) 1 error
原因是我们不能保证列表最初是为哪种类型的元素构造的。'?extends Number'表示任何扩展Number的类型,但编译器永远不知道是什么类型。我们只能向此类集合引用添加null,或者从中删除项目,但我们不能向其添加新元素。
带有通配符'?super'的Java泛型是逆变的:
Java泛型允许用'? super'通配符将超类型替换为子类型:
public class ContravariantGenericsExample {
public static void main(String[] args) {
List<A> aList = new ArrayList<>();
aList.add(new A());
List<? super B> bList = aList;
bList.add(new B());
System.out.println(bList);
}
private static class A {
}
private static class B extends A {
}
}
输出
[com.logicbig.example.ContravariantGenericsExample$A@436e27a6, com.logicbig.example.ContravariantGenericsExample$B@69c3928e]
请注意,在上述情况下,我们在赋值后能够添加类型B的元素。这是因为,编译器知道bList是B的任何超类型的列表,而B(或B的子类型)肯定可以始终分配给B的超类型。
另一方面,从'? super B',编译器将永远不知道B的哪个超类型,这意味着我们不能向bList添加类型A或A的其他子类型的任何元素。
Java中的协变方法返回类型
Java允许子类覆盖可以返回更特定类型的方法。换句话说,子类方法的返回类型必须能够替换超类方法的返回类型。
方法的协变返回类型是当方法在子类或子接口中被覆盖时,可以被“更窄”的类型(子类型)替换的类型。
换句话说,被覆盖的方法可以有更具体的返回类型。只要新的返回类型可以赋值给我们正在覆盖的方法的返回类型,它就可以被使用。
Java自5.0版本以来就支持这个特性。
public class CovariantReturnExample {
interface SuperType {
}
interface SubType extends SuperType {
}
interface A {
SuperType getType ();
}
interface B extends A {
SubType getType ();
}
}
注意:协变返回可以在扩展类或扩展接口时使用。在上述示例中,SuperType-SubType 和/或 A-B 可以是类。