逆变
除了使用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
了。