这两天在接触泛型的时候,发现这个泛型的上下界比较抽象,不好理解。经过查阅资料,解决了这个问题,现在分享一下心得。
泛型上下界的介绍
?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、如果既要存又要取,那么就不要使用任何通配符。