菜鸟先飞之JAVA_泛型上下界详谈

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Happy_cloudlife/article/details/77529429
这两天在接触泛型的时候,发现这个泛型的上下界比较抽象,不好理解。经过查阅资料,解决了这个问题,现在分享一下心得。

泛型上下界的介绍
?exdends E:接收E类型或者E的子类型对象,上界。
?super E:接收E类型或者E的父类型,下界。

上下界的使用场景
一般在存储元素的时候都是用上界,因为这样取出都是按照上界类型来运算的。不会出现类型的安全隐患。
通常对集合中的元素进行取出操作时,可以是用下界。

泛型上界使用举例
首先定义3个been,分别是Animal、Dog、Teddy。其中Dog继承了Animal,Teddy继承了Dog。
public class Animal {
	private String name;
	private Integer age;
	
	public Animal(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "[name=" + name + " , age=" + age + "]";
	}

}

class Dog extends Animal {

	public Dog(String name, Integer age) {
		super(name, age);
		
	}

}

class Teddy extends Dog {

	public Teddy(String name, Integer age) {
		super(name, age);
		
	}

}

通过查询API我们可以知道,在Collection接口中有这么一个方法。
boolean addAll(Collection<? extends E> c)
将指定 collection 中的所有元素都添加到此 collection 中(可选操作)。
public class demo1 {
	/*
	 * 限定上界
	 * ?extends E
	 * 
	 * boolean addAll(Collection<? extends E> c)
	 * 将指定 collection 中的所有元素都添加到此 collection 中(可选操作)。
	 */
	public static void main(String[] args) {
		ArrayList<Animal> animalList = new ArrayList<>();
		animalList.add(new Animal("animal1",11));
		animalList.add(new Animal("animal2",12));
		animalList.add(new Animal("animal3",13));
		
		ArrayList<Dog> dogList = new ArrayList<>();
		dogList.add(new Dog("dog1",21));
		dogList.add(new Dog("dog2",22));
		dogList.add(new Dog("dog2",23));		
		
		ArrayList<Teddy> teddyList = new ArrayList<>();
		teddyList.add(new Teddy("teddy1",31));
		teddyList.add(new Teddy("teddy2",32));
		teddyList.add(new Teddy("teddy3",33));
		
		ArrayList<String> stringList = new ArrayList<>();
		stringList.add("str1");
		stringList.add("str1");
		stringList.add("str1");
		
//		dogList.addAll(animalList);// 报错The method addAll(Collection<? extends Dog>) in the type ArrayList<Dog> is not applicable for the arguments (ArrayList<Animal>)
		
//		dogList.addAll(stringList);// 报错The method addAll(Collection<? extends Dog>) in the type ArrayList<Dog> is not applicable for the arguments (ArrayList<String>)
		
		dogList.addAll(teddyList);
		System.out.println(dogList.size());  // 结果是6
		
	}

}

通过上面的结果我们可以看出来,Collection集合的addAll方法接收的参数类型泛型被? extends E限定之后,只能接收E和E的子类对象。如果别的类型的对象要添加进这个集合,就会在编译时报错。这样限定后就能确保,集合中只有我们想要的对象类型。

泛型下界使用举例
我们还使用上面的3个been。然后我们在API中找到TreeSet,发现它有这么一个构造方法。
public TreeSet(Comparator<? super E> comparator)
构造一个新的空 TreeSet,它根据指定比较器进行排序。插入到该 set 的所有元素都必须能够由指定比较器进行相互比较。
那我们就先创建一个比较器。根据对象的name进行排序。
public class CompByName implements Comparator<Dog> {

	@Override
	public int compare(Dog a1, Dog a2) {
		int num = a1.getName().compareTo(a2.getName());		//比较名字的大小
		return num == 0 ? a1.getAge()-a2.getAge() : num; 	//如果名字相同比较年龄
	}

}

调用比较器排序
public class demo2 {

	public static void main(String[] args) {
		TreeSet<Teddy> teddySet = new TreeSet<>(new CompByName());
		teddySet.add(new Teddy("abc",11));
		teddySet.add(new Teddy("aac",12));
		teddySet.add(new Teddy("abc",13));
		
		TreeSet<Dog> dogSet = new TreeSet<>(new CompByName());
		dogSet.add(new Dog("bbc",21));
		dogSet.add(new Dog("bac",22));
		dogSet.add(new Dog("bbc",23));
				
//		TreeSet<Animal> animalSet = new TreeSet<>(new CompByName());//报错Type mismatch: cannot convert from TreeSet<Dog> to TreeSet<Animal>
//		animalSet.add(new Animal("animal1",1));
		
//		TreeSet<String> stringSet = new TreeSet<>(new CompByName());//报错Type mismatch: cannot convert from TreeSet<Animal> to TreeSet<String>
//		stringSet.add("str1");
		
		System.out.println(teddySet);//结果:[[name=aac , age=12], [name=abc , age=11], [name=abc , age=13]]
		System.out.println(dogSet);//结果:[[name=bac , age=22], [name=bbc , age=21], [name=bbc , age=23]]
		
	}

}

通过上面的结果我们可以看出来,Dog对象集合也能通过CompByName这个比较器进行比较,所以当比较器被? super E限定之后,这个比较器既可以提供给Dog和Dog的子类去做比较。当看到这个地方的时候肯定会有疑问,这个限定下限和限定上限有什么区别。接下来我们说一下java中的PECS法则。

PECS法则的概述
PECS指“Producer Extends,Consumer Super”。用我们的说,如果参数化类型表示一个生产者,就使用<? extends E>;如果它表示一个消费者,就使用<? super E>。

<? extends E>上界通配符
这个拿到我们的demo1中来说,就是Collection<? extends Dog>,意思就是Collection这个集合可以存放Dog类以及Dog类的所有子类。所以在dogList中添加animalList的全部内容的时候,会在编译的时候报错。

<? super E>下界通配符
这个拿到我们demo2中来说,就是Comparator<? super E> comparator,对于dogSet来说只能用(Comparator<? super Dog> comparator),调用的是自己的比较器所以通过,但是对于teddySet来说只能用(Comparator<? super Teddy> comparator),调用的是自己父类的比较器所以通过,对于animalSet来说只能用(Comparator<? super Animal> comparator),调用的是自己子类的比较器,所以会在编译的时候报错。

PECS法则总结
1、如果要从集合中读取类型E的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)
2、如果要从集合中写入类型E的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
3、如果既要存又要取,那么就不要使用任何通配符。


猜你喜欢

转载自blog.csdn.net/Happy_cloudlife/article/details/77529429
今日推荐