界定通配符
协变
public class ArraysCovariant {
public static void main(String[] args) {
class Animal {}
class Bird extends Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
Animal[] animal = new Animal[20];
animal[0] = new Bird(); // OK
animal[1] = new Dog(); // OK
try {
animal[0] = new Animal(); //OK
} catch(Exception e) {
System.out.println(e);
}
try {
animal[0] = new Animal(); // OK
} catch(Exception e) {
System.out.println(e);
}
}
}
animal 为数组,每个元素为 Animal,由于子类可以赋值给父类,因此,这里没有任何问题。这就是数组的协变。显然,协变带来了不确定性,让程序无法按照预期运行。
集合不支持协变。
List<Animal> animal = new ArrayList<Bird>(); // 报错
为了让泛型在特殊情况下支持协议,因此,出现了界定通配符。
界定通配符
Vector<? extends 类型1> x = new Vector<类型2>(); //上边界:类型2就只能是类型1或者是类型1的子类
Vector<? super 类型1> x = new Vector<类型2>(); //下边界:类型2就只能是类型1或者是类型1的父类
一个例子彻底理解界定通配符
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
//一个基本的前提:父类赋值给子类,而子类可以赋值给父类
public class GenericTest {
public static void main(String[] args) throws Exception {
class Animal {}
class Bird extends Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
List<Animal> tmpList = new ArrayList<>();
tmpList.add(1);
tmpList.add(2);
tmpList.add(3);
// extendsList 元素的类型是 Animal 或其子类。
List<? extends Animal> extendsList = tmpList;
// add,set 的参数包括泛型,要将 Bird 转换为 Animal 的子类,由于具体子类不清楚,这是错误的
extendsList.add(new Bird('a'));
extendsList.set(1, new Bird('b'));
// remove 的参数是 Object,不是泛型,因此没问题
extendsList.remove(new Bird('a'));
extendsList.contains(new Bird('b'));
// get 的返回值为泛型 Animal 的子类,可以转换为父类 Animal
Animal extendsGet = extendsList.get(1);
// superList 元素的类型是 Animal 或其父类。
List<? super Animal> superList = tmpList;
// add,set 的参数包括泛型,要将 Integer 转换为 Number 的父类,这是没问题的
superList.add(new Bird('a'));
superList.set(1, new Bird('b'));
// remove, contains 的参数是 Object,不是泛型,因此没问题
superList.remove(new Bird(1));
superList.contains(1);
// get 返回值为泛型 Animal 的父类,Animal 的父类不可以转换为 Animal
Animal superGet = superList.get(1);
}
特殊情况说明
//Collections 的 sort 方法
public class Collections {
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
}
这里泛型类型是 T
- T 要支持排序,必须实现 Comparable 接口,因此 T extends Comparable
- 为了 T 及子类能够使用 Comparable 接口,因此 T extends Comparable<? extends T>
通配符 与 T 的区别
T:作用于模板上,用于将数据类型进行参数化,不能用于实例化对象。
?:在实例化对象的时候,不确定泛型参数的具体类型时,可以使用通配符进行对象定义。
总结
- 限定通配符总是包括自己
- 上界类型通配符:参数是泛型的方法受限(很多文章都没有说清楚这里)
- 下界类型通配符:返回值为泛型的方法受限(很多文章都没有说清楚这里)
- 如果你想从一个数据类型里获取数据,使用 ? extends 通配符
- 如果你想把对象写入一个数据结构里,使用 ? super 通配符
- 如果你既想存,又想取,那就别用通配符
- 不能同时声明泛型通配符上界和下界