逆变
除了使用extends通配符外,还可以使用超类型通配符。这里,可以声明通配符是由某个特定类的任何基类来界定的,方法是指定<? super MyClass>,甚至或者使用类型参数:<? super T>(尽管不能对泛型参数给出一个超类型边界);即不能声明<T super MyClass>。这使得可以安全地传递一个类型对象到泛型参数中。因此,有了超类型通配符,就看向Collection写入了。
public class SuperTypeWildcards {
static void writeTo(List<? super Apple> apples) {
apples.add(new Apple());
apples.add(new Jonathan());
// apples.add(new Fruit()); Error
}
}
参数apples是Apple的某种基类型的List,所以向里面添加Apple或者Apple的子类型是安全的。但是既然Apple是下界,那么向这样的List添加Fruits是不安全的。因为这会使List敞开口子,从而可以向里面添加非Apple类型的对象,而这样是违反静态类型安全的。
因此代码中可能会根据如何能够向一个泛型类型“写入”(传递给一个方法),以及如何能够从一个泛型类型中“读取”(从一个方法中返回),来着手思考子类型和超类型边界。
超类型边界放松了在可以向方法传递的参数上的限制。
public class GenericWriting {
static <T> void writeExact(List<T> list, T item) {
list.add(item);
}
static List<Apple> apples = new ArrayList<>();
static List<Fruit> fruits = new ArrayList<>();
static void f1() {
writeExact(apples, new Apple());
writeExact(fruits, new Apple());
}
static <T> void writeWithWildcard(List<? super T> list, T item) {
list.add(item);
}
static void f2(){
writeWithWildcard(apples,new Apple());
writeWithWildcard(fruits,new Apple());
}
public static void main(String[] args) {
f1();
f2();
}
}
上面的代码中,f1()和f2()都能很好的工作(在Java7版本之后)。我们可以很安全的将Fruit及其子类型作为'Fruit或者<? super Fruit>`的参数。
下面是一段类型分析。
public class GenericReading {
static <T> T readExact(List<T> list) {
return list.get(0);
}
static List<Apple> apples = Arrays.asList(new Apple());
static List<Fruit> fruits = Arrays.asList(new Fruit());
static void f1() {
Apple a = readExact(apples);
Fruit f = readExact(fruits);
f = readExact(apples);
}
static class Reader<T> {
T readExact(List<T> list) {
return list.get(0);
}
}
static void f2() {
Reader<Fruit> fruitReader = new Reader<>();
Fruit f = fruitReader.readExact(fruits);
// Fruit a = fruitReader.readExact(apples); Error
}
static class CovariantReader<T> {
T readCovariant(List<? extends T> list) {
return list.get(0);
}
}
static void f3() {
CovariantReader<Fruit> fruitReader = new CovariantReader<>();
Fruit f = fruitReader.readCovariant(fruits);
Fruit a = fruitReader.readCovariant(apples);
}
public static void main(String[] args) {
f1();
f2();
f3();
}
}
上面的代码中,f1()用了精确的类型,因此如果使用了这个没有任何通配符的精确类型,就可以向List中写入和读取这个精确类型。另外,对于返回值,静态的泛型方法readExact()可以有效地“适应”每个方法调用,并能够从List<Apple>中返回一个Apple,从List<Fruit>中返回一个Fruit,就像在f1()中看到的那样。因此,只是读取的时候,静态的泛型方法不需要协变类型。
但是,如果有个泛型类,那么当创建这个类的实例的时候,要为这个类确定类型参数。就像在f2()中看到的那样,fruitReader可以从List<Fruit>中读取一个Fruit,因为这就是它的确切类型,但是List<Apple>还应该产生Fruit对象,但是fruitReader不允许传入List<Apple>参数,因为参数是List<Fruit>类型,编译器不允许发生这种转换。
而为了修正这个问题,CovariantReader.readCovariant()方法将接受List<? extends T>,因此,从这个列表中读取一个T是安全的(因为这个列表的所有对象至少是一个T,并且可能是T导出来的对象),所以在f3()中,可以从List<Apple>中读取Fruit了。