通配符的理解

首先写几个类

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"));

上面所示的代码就不能通过编译,就是因为编译器不能确定<?extends Animal>到底代表什么类,就索性不让往里面添加任何的元素或只能添加null
<hr>
还有一个要知道的通配符就是<? super T> 通配符的上界  注意:不能同时声明泛型通配符申明上界和下界。

对于通配符的上界,有以下几条基本规则:(假设给定的泛型类型为G,(如List<E>中的List),两个具体的泛型参数X、Y,当中Y是X的子类(如上的Animal和Cat))
  • 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值