为什么在集合中用泛型
泛型与集合的联合使用,可以把泛型的功能发挥到极致.
- List、List、List<?> 三者的区别?
- 怎么区分<? extends T> 与 <? super T> 的使用场景?
List 完全没有类型限制和赋值限定, 如果天马行空地乱用,迟早会遭遇类型转换失败的异常. 很多程序员觉得List 的用法完全等同于List, 但在接受其他泛型赋值时会编译出错. List<?> 是一个泛型,在没有赋值之前,表示它可以接受任何类型的集合赋值,赋值之后就不能随便往里面添加元素了.
下方的例子说明了三者的区别,以List为原型展开说明:
public class ListNoGeneric {
public static void main(String[] args) {
// 第一段: 泛型出现之前的集合定义方式
List a1 = new ArrayList();
a1.add(new Object());
a1.add(new Integer(111));
a1.add(new String("hello a1"));
// 第二阶段: 把a1引用赋值给a2,注意a2与a1的区别是增加了泛型限制<Object>
List<Object> a2 = a1;
a2.add(new Object());
a2.add(new Integer(222));
a2.add(new String("hello a2"));
// 第三段: 把a1 引用赋值给a3,注意a3与a1的区别是增加了泛型<Integer>
List<Integer> a3 = a1;
a3.add(new Integer(333));
// 下方两行编译错误,不允许增加费Integer类型进入集合
// a3.add(new Object());
// a3.add(new String("hello a3"));
// 第四段: 把a1引用赋值给a4,a1与a4 的区别是增加了通配符
List<?> a4 = a1;
// 允许删除和清除元素
a1.remove(0);
a4.clear();
// 编译出错,不允许增加任何元素
// a4.add(new Object());
}
}
第一段说明: 在定义List之后,毫不犹豫的往集合里装入三种不同的对象: Object, Integer和String, 遍历没有问题,但是贸然以为里面的元素都是Integer,使用强制转换,则抛出 ClassCastException 异常.
第二段说明: 把a1 赋值给a2, a2 是List 类型的,也可以再往里装入三种不同的对象. 但是如果在第三段中 List<Integer> a3 = a2;
是会出现编译错误,所有List和List 是不完全相同的.
第三段说明: 由于JDK5之后才出现泛型,考虑到向前兼容,因此历史代码有时需要赋值给新泛型代码,从编译器角度是允许的.
下面是一段问题代码:
JSONObject jsonObject = JSONObject.formObject("{\"level\":[\"3\"]}");
List<Integer> intList = new ArrayList<>(10);
if(jsonObject != null){
intList.addAll(jsonObject.getJSONArray("level"));
int amount = 0;
for(Integer t : intList){
// 抛出 ClassCaseException 异常 : String cannot be cast to Integer
if(condition){
amount = amount + t;
}
}
}
addAll 的定义如下:
public boolean addAll(Collection<? extends E> c){...}
进行了泛型限制,示例中addAll 的实际参数时 getJSONArray 返回的 JSONArray 对象,它并非为List,更加不是Integer 集合的子类,但为何编译不报错?查看JSONArray 的定义:
pubic final class JSONArray extends AbstractJSON implements JSON,List{}
JSONArray 实现了List, 是非泛型集合,可以赋值给任何泛型限制的集合. 编译可以通过,但在运行时报错,这个一个隐藏得很深的Bug,最终导致发生线上故障. 在JDK5之后,应尽量使用泛型定义, 以及使用类, 集合, 参数等.
List 赋值给 List 是不允许的,若反过来赋值:
List<Integer> intList = new ArrayList<>(3);
intList.add(111);
List<Object> objects = intList;
事实上,依然会编译错误.
注意: 数组可以这样赋值,因为它是协变的,而集合不是.
第四段说明: 问号在正则表达式可以匹配任何字符,List<?> 称为通配符集合. 它可以接受任何类型的集合引用赋值,不能添加任何元素, 但是可以remove 和 clear 操作, 并非 immutable 集合. List<?> 一般作为参数来接收外部的集合,或者返回一个不知道具体类型的集合.
List 最大的问题是只能放置一种类型,如果随意转换类型的话,就是破窗理论, 泛型就失去了类型的安全意义.
**<? extends T> 与<? super T>**两种语法:
<? extends T> 是Get First , 适用于,消费集合元素为主的场景; <? super T> 是 Put First , 适用于,生产集合元素为主的场景.