首先写几个类
public class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(getName() + " can eat.");
}
public String getName(){
return name;
}
}
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
public void jump(){
System.out.println(getName() + " can jump.");
}
}
public class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly(){
System.out.println(getName() + " can fly.");
}
}
public class Magpie extends Bird{
public Magpie(String name) {
super(name);
}
public void sing(){
System.out.println(getName() +
" can not only eat,but sing");
}
}
public class AnimalTrainer {
public void act(List<Animal> list) {
for (Animal animal : list) {
animal.eat();
}
}
}
做好如上准备后,写一个测试类
public class TestAnimal {
public static void main(String[] args) {
AnimalTrainer animalTrainer = new AnimalTrainer();
//Test 1
List<Animal> animalList = new ArrayList<>();
animalList.add(new Cat("cat1"));
animalList.add(new Bird("bird1"));
animalTrainer.act(animalList); //可以通过编译
//Test 2
List<Cat> catList = new ArrayList<>();
catList.add(new Cat("cat2"));
catList.add(new Cat("cat3"));
//这是因为尽管Cat是Animal的子类,但是List<Animal>和List<Cat>并没有任何关系,所以无法通过编译
animalTrainer.act(catList); //无法通过编译
}
}
编译器无法通过Test2的编译,这是因为尽管从前面代码可以得到Animal是Cat的父类,但是LIst<Animal>和List<Cat>之间并没有任何关系,两者是平行的,所以也就不能作为参数传入了,而在Test1当中,首先是new了List<Animal>对象,由与Cat类是Animal的子类,所以Cat对象可以add进List<Ainmal>集合的对象当中,这点是不产生冲突的。
<hr>
为了让AnimalTrainer这个工具方法更加的适用,可以使用通配符来扩展,具体操作如下。
public class AnimalTrainer {
public void act(List<? extends Animal>list) {
for (Animal animal : list) {
animal.eat();
}
}
}
将AnimalTrainer类中的方法改为如上所示,增加通配符List<? extends Animal> 表示可以添加任何Animal 及Animal的子类的泛型。注意for循环,是用Animal表示,所有List当中的元素,无论以前是什么类型,全部当作Animal类型进行处理,这是因为只要是能编译通过的,都是Animal的子类,这也体现了多态
对于通配符的上界,有以下几条基本规则:(假设给定的泛型类型为G,(如List<E>中的List),两个具体的泛型参数X、Y,当中Y是X的子类(如上的Animal和Cat))
- G<? extends Y> 是 G<? extends X>的子类型(如List<? extends Cat> 是 List<? extends Animal>的子类型)。
- G<X> 是 G<? extends X>的子类型(如List<Animal> 是 List<? extends Animal>的子类型)
- G<?> 与 G<? extends Object>等同,如List<?> 与List<? extends Objext>等同。
还要一点需要注意的是,<? extends Y> 这类通配符,不能往里添加任何元素,如果要添加,也只能添加null
List<? extends Animal> list = new ArrayList<>();
list.add(new Animal("animal"));
list.add(new Bird("bird"));
list.add(new Cat("cat"));
<hr>
还有一个要知道的通配符就是<? super T> 通配符的上界
注意:不能同时声明泛型通配符申明上界和下界。
- G<? super X> 是 G<? super Y>的子类型(如List<? super Animal> 是 List<? super Bird>的子类型)。
- G<X> 是 G<? super X>的子类型(如List<Animal> 是 List<? super Animal>的子类型)
观察如下代码
List<? super Bird> list1 = new ArrayList<>();
list1.add(new Bird("bird"));
list1.add(new Magpie("magpie"));
list1.add(new Animal("animal"));//这条不能通过编译
上面的代码是new了一个ArrayList()类,并且配上了<? super Bird>通配符,<? super Bird>就代表着泛型约束为: Bird类及其父类 ,之所以第三条add语句不能通过编译,是因为,在试图往里边添加一个Animal对象,而编译器不知道<? super Bird> 泛型约束是代表着实 Bird类,还是 Animal类,Object类 或者其他Bird类的约束,由于安全的考虑,就不予以编译通过。可以将<? super Bird> 改为 < ? super Animal> 这样第三条语句就可以编译通过了!
那么如果遍历List<? super Bird> list1 这个对象呢?
for(Object b:list1)
总的来说,<? extends T> 不能往其中添加元素,因为他规定了泛型的上界为T 并且为指明具体是那类,所以分情况考虑,如如果是与T无关的,肯定不能放进去;T的父类,也不能,这不符合多态子类可以指向父类引用,而父类对象不能指向子类引用。接着就是T的子类,因为编译器不知道具体是哪类子类,为了避免出错,就不允许放进去。换个角度考虑,如果我们通过
反射 技术 将不同的元素放进他们共同的父类当中,取出来的时候,就容易发生ClassCastException异常,这不符合java语言规范
接着讨论<? super T> 理论上来说,它既可以添加元素,也可以取出元素。首先添加元素只要是T及T的子类都可以添加进去。至于取出元素,因为无法判断父类的类型(可以是T,也可以是T的父类)所以通通用Object指向对象。
我们可以使用通配符,对方法的参数进行保护。防止错误操作污染数据源
文章参考url:http://www.linuxidc.com/Linux/2013-10/90928p4.htm
? super T
? super T