在java泛型的使用过程中常常需要使用通配符,最常用的就是使用<?>这个无限定通配符。很多时候使用泛型时都不依赖于类型参数T中的方法,但如果使用object类的话又会受到很多限制,这时候就会用到<?>
比如说一下这个函数
public void printList(List<Object> list)
{
for (Object e : list)
System.out.println(e + " ");
}
本来想打印任意的List,结果发现能够输入的只有List ,这是因为在java中泛型是不协变的,这确实导致了很多的麻烦,但是将代码改为List<?>后,就可以接受任意类型List。
前面说了泛型是不能协变的,所以为了实现协变,可以使用有限定的通配符。同样是打印List
public void printList(List<Resource> list)
{
for (Resource e : list)
System.out.println(e + " ");
}
上面这个函数可以打印Resource类型List,但对于Resource子类型的List却无法打印,原因前面说了两遍了,泛型是不能协变的,但是将代码改为
public void printList(List<? extends Resource> list)
{
for (Resource e : list)
System.out.println(e + " ");
}
就能解决这个问题。
另外还有一个可能使用的通配符是上限通配符 <? super A>,比如List<? super Cat> b = new ArrayList<>();不过有违直观印象的是,这个数组看上去能够存储的是Cat的父类型,b.add(new Animal());这个代码却是会出错的,反而b.add(new WhiteCat());这个才是对的,这是为什么呢,其实可以这样思考:因为你告诉编译器List中存储的是Cat父类型,但编译器却不知道要添加的是哪一个父类型,但当你添加的是Cat子类型的时候,由于你告诉编译器下界是Cat,所以不管输入什么子类型那么它一定都是一种Cat。再来看get方法就更能认清这一点,事实上只有Object o1 = b.get(0);能够编译通过,因为你告诉编译器下界是Cat,但上界是完全不确定的,所以用除了Object之外的任何类型来接受都可能会出问题。所以总结下来<? super A>能addA的子类型,get只能是Object类型。
相反的,有List<? extends Cat> animal,它所能add的只有null,因为你告诉编译器它能存储Cat的子类型,编译器却不知道要添加的是哪种子类型,就会报错。但是像Cat s1 = animals.get(0); Animal s2 = animals.get(0); 却是可以的,因为上界是Cat,所以返回值用Cat和Animal接收一定是可行的。