泛型
一句话总结:对类型实现了参数化
一般使用固定字母表征,如T,K,V,E,其原理是类型擦除,在进入JVM前,这些指代字母都会被替换为Object,后面再通过强制类型转化变为期望的类型。
经典的例子:我们常用的集合类,如List中的Integer就是对参数化的类型进行赋值,将原本的类型变量转为一个固定的Integer类型。
通配符
为什么要有通配符:通配符的出现是为了解决集合无法协变的问题。
协变:协变指的就是如果 Student 是Person的子类,那么 List 也应该是 List 的子类。
Number num = new Integer(10); // compile success
ArrayList<Number> list = new ArrayList<Integer>(); // compile error
实现泛型协变和逆变主要是 extends 和 super 关键字,例如:
< ? extends T>实现泛型协变
ArrayList<? extends Number> list = new ArrayList<Integer>();
< ? super T>实现泛型逆变
ArrayList<? super Number> list2 = new ArrayList<Object>();
使用extends和super的时机:
PECS原则——producer-extends, consumer-super
如果是从集合中读取元素,使用extends,因为extends确定了上界,而元素能够向上转型,所有可以得到上界类型的引用。对集合来说,集合作为生产者生产元素供读取,所以是producer-extends.
如果是向集合中写入元素,使用super,因为super确定了下界。但是在实际使用中,只可以向集合中写入元素的子类,因为只有向集合中写入元素理解为集合消费元素。
实验代码:
public class PECS_test {
public static void main(String[] args) {
List<Fruit> fruits = new ArrayList<>();
List<? super Apple> fruits2 = fruits;
List<? extends Fruit> fruits3 = fruits;
//如果只是增加元素,集合支持自动向上转型
fruits.add(new Apple(1,1,2));
//编译器警告 虽然是super修饰 但是并不能直接加入它的父类
//fruits2.add(new Fruit(1,2));
fruits2.add(new Apple(1,2,2));
fruits2.add(new GreenApple());
fruits2.add(new GreenApple());
//使用extends的,只可以读,不可以写
//fruits3.add(new Apple(1,2,3));
//fruits3.add(new Fruit(1,2));
Fruit f = fruits3.get(0);
}
}
class Fruit{
public Fruit(int price, int number){
this.price = price;
this.number = number;
}
int price;
int number;
}
class Apple extends Fruit{
public Apple(int price, int number, int size){
super(price, number);
this.size = size;
}
int size;
}
class GreenApple extends Apple{
public GreenApple(){
super(1,1,1);
}
}
泛型和通配符对比
泛型更像是个参数,一旦确定就只有一个值,通配符更加灵活,用于确定一个范围。
参考资料:
① https://www.cnblogs.com/czwlinux/p/17321493.html
②https://blog.csdn.net/WTUDAN/article/details/125781490