非泛型容器:List a = new ArrayList();
泛型容器:List<Object> a = new ArrayList<>();
java的非泛型容器,已经从1.4.2占有到5.0,市面上已经有大量的代码,不得已选择了第二种方法。(之所以是从1.4.2开始,是因为java以前连collection都没有,是一种vector的写法。),而且有一个更重要的原因就是之前提到的向后兼容。所谓的向后兼容,是保证1.5的程序在8.0上还可以运行。(当然指的是二进制兼容,而非源码兼容。)所以本质上是为了让非泛型的java程序在后续支持泛型的jvm上还可以运行。
类型擦除:
java的泛型在底层实现上使用了Object引用,也就是我们之前所提到的第二种方式,但是为了防止你往一个Apple的Bucket添加一个Banana。编译器会先根据你声明的泛型类型进行静态类型检查,然后再进行类型擦出,擦除为Object(擦除是把List<Bucket>和List<Apple>擦除成为List<Object>,这里并不是把里面的元素擦除)。而所谓的类型检查,就是在边界(对象进入和离开的地方)处,检查类型是否符合某种约束,简单的来说包括:
赋值语句的左右两边类型必须兼容。
函数调用的实参与其形参类型必须兼容。
return的表达式类型与函数定义的返回值类型必须兼容。
协变和逆变
static class Grandfather {}
static class Father extends Grandfather {}
static class Son extends Father {}
static class Daughter extends Father {}
协变< ? extends T>
类型的上界是 T,参数化类型可能是 T 或 T 的子类:
List<? extends Father> listF = new ArrayList<>();
List<Son> sonList = new ArrayList<>();
listF = sonList;
Father son = listF.get(0);
listF 此时,listF=listSon不会报错。
类型的上界是 Father,参数化类型可能是 Father 或 Father 的子类。尖括号里只能是Father的子类
也就是说,如果Father还有一个子类Daughter,那么
listF = new ArrayList<Daughter>();
Father daughter = listF.get(0);//get出来的还是Father
这里并不能向listF中添加元素
不能添加任何元素,因为无法确认listP 具体类型,不能进行类型检查。
逆变<? super T>
表示类型的下界是 T,参数化类型可以是 T 或 T 的超类:
List<? super Father> listF = new ArrayList<>();
List<Grandfather> grandfathers = new ArrayList<>();
listF = grandfathers;
listF.add(new Father());
listF.add(new Son());
仅能添加元素,不能取元素。此时不能确定listF到底是Father的哪个超类的集合。所以添加的元素需是Father或者Father的子类,因为这样类型检查时才能通过(将填进去的元素向上转型为Father,可以确定的是listF的元素一定是Father和Father子类)。
总结
上届和下届,都是针对于集合的元素类型,而不是添加进去的具体元素。
PECS(Producer Extends Consumer Super)原则:
生产者 <? extends T>只能读取元素,一直从其中拿,他就是生产者。
消费者 <? super T>只能写入元素,一直向其中写,他就是消费者。
参考:http://www.itxm.cn/post/4185.html
参考:https://blog.csdn.net/xx326664162/article/details/52175283