泛型中包含限定通配符和非限定通配符。
- 限定通配符
存在两种形式:一种是<? extends T>
它通过确保类型必须是T的子类来设定类型的上界,另一种是
<? super T>
它通过确保类型必须是T的父类来设定类型的下界。 - 非限定通配符
<?>
表示了非限定通配符,因为<?>
可以用任意类型来替代
1、通配符上界<? extends T>
来一个具体泛型类。
public interface Collection<E> extends Iterable<E> {
....
public boolean addAll(Collection<? extends E> collection);
....
}
针对上面addAll方法分析三种情况
第一种情况:
List<Number> nums = new ArrayList<Number>();
List<Integer> ints = Arrays.asList(1, 2);
List<Double> dbls = Arrays.asList(2.78, 3.14);
nums.addAll(ints); //ok
nums.addAll(dbls); //ok
在代码中我们可以看到,List<Integer>
和List<Double>
都是 Collection<? extends Number>
类型的子类。所以上面的方法中可以将Integer和Double两种类型的List传入到方法中。
第二种情况:
List<? extends Number> nums = new ArrayList<Integer>(); //compile error
上面这段代码是编译通不过的。
第三种情况:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(3.14); // compile error
这段代码的第5行会导致编译错误。在第4行代码中,我们将ints赋值给nums,表面上nums声明为一个 List<Integer>
的父类型,所以第4行编译正常。
为什么第5行代码会出错呢?表面上看来,既然nums类型可以接受继承自 Number的所有参数,那加一个Double类型的数据应该是没问题的。实际上我们再考虑一下这样会带来的问题:
nums本来引用的是一个继承自该类型的List<Integer>
,如果我们允许加入Double类型的数据的话,那么ints这个 Integer的List里面就包含了Double的数据,当我们使用ints的时候,和我们所期望的只包含Integer类型的数据不符合。
因此,这段代码也说明了一个问题,就是在<? extends E>
这种通配符引用的数据类型中,如果向其中增加数据操作的话会有问题。所以向其中增加数据是不允许的。但是我们可以从其中来读取数据。
总结如下:
<? extends E>
修饰的泛型不能用来直接创建变量对象。- 禁止向
<? extends E>
添加数据(除了null) <? extends E>
修饰相当于声明了一种变量,它可以作为参数在方法中传递。这么做带来的好处就是我们可以将应用于包含某些数据类型的列表的方法也应用到包含其子类型的列表中。相当于可以在列表中用到一些面向对象的特性。
2、通配符下界 <? super T>
第一种情况:
看如下代码,判断其是否符合逻辑:
public void testAdd(List<? super Bird> list){
list.add(new Bird("bird"));
list.add(new Magpie("magpie"));
}
List<? super Bird> list = new ArrayList<>();
list.add(new Bird("bird")); //ok
list.add(new Magpie("magpie")); //ok
list.add(new Animal("animal")); // error
在解疑之前,再来强调一个知识点,子类可以指向父类,即Bird也是Animal对象。现在考虑传入到testAdd()的所有可能的参数,可以是List<Bird>
,List<Animal>
,或者List<Object>
等等,发现这些参数的类型是Bird或其父类,那我们可以这样看,把bird、magpie看成Bird对象,也可以将bird、magpie看成Animal对象,类似的可看成Object对象,最后发现这些添加到List<? supe Bird> list
里的对象都是同一类对象,因此testAdd方法是符合逻辑,可以通过编译的。:
现在再来看一下第二段代码对于,第二、三行代码的解释和上文一样,至于最后一行“list.add(new Animal(“animal”))”是无法通过编译的,为什么的??为了保护类型的一致性,因为<? super Bird>
可以是Animal,也可以是Object或其他Bird的父类,因无法确定其类型,也就不能往List<? super Bird>
添加Bird的任意父类对象。
第二种情况:
既然无法确定其父类对象,那该如何遍历List<? super Bird>
。因为Object是所有类的根类,所以可以用Object来遍历。如下,不过貌似其意义不大。
for (Object object : list) {//...}
总结如下:
- 可以添加子类型
- 不能确定类型,只能当作Object对象来处理
用途:
public class Student implements Comparable<Student>{
private int id;
public Student(int id) {
this.id = id;
}
@Override
public int compareTo(Student o) {
return (id > o.id) ? 1 : ((id < o.id) ? -1 : 0);
}
}
public class CollegeStudent extends Student{
public CollegeStudent(int id) {
super(id);
}
}
public static <T extends Comparable<? super T>> void selectionSort(T[] a,int n)
先理解此方法含义,首先<T extends Comparable<T>>
规定了数组中对象必须实现Comparable接口,Comparable<? Super T>
表示如果父类实现Comparable接口,其自身可不实现,如CollegeStudent。先假设有一个CollegeStudent的数组,如下:
CollegeStudent[] stu = new CollegeStudent[]{
new CollegeStudent(3),new CollegeStudent(2),
new CollegeStudent(5),new CollegeStudent(4)};
执行方法 selectionSort(stu,4)是完全可以通过的。
可如果定义的selectionSort方法如下,则方法selectionSort(stu,4)不能执行,因为CollegeStudent没有实现Comparable<CollegeStudent>
接口。
public static <T extends Comparable<T>> void selectionSort(T[] a,int n)
3、无界通配符 <?>
知道了通配符的上界和下界,其实也等同于知道了无界通配符,不加任何修饰即可,单独一个“?”。如List<?>
,“?”可以代表任意类型,“任意”也就是未知类型。
当方法是使用原始的Object类型作为参数时,如下:
public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
可以选择改为如下实现:
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
这样就可以兼容更多的输出,而不单纯是List<Object>
,如下:
List<Integer> li = Arrays.asList(1, 2, 3);
List<String> ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);
最后提醒一下的就是,List<Object>
与List<?>
并不等同,List<Object>
是List<?>
的子类。还有不能往List<?>
list里添加任意对象,除了null。