泛型的本质是类型参数化,是为了解决不确定具体对象类型时用到的办法,它的出现是一项伟大的发明,不仅解决了类型的校验问题,还可以提高代码的复用性。然而集合与泛型的联合使用,可以把泛型的功能发挥到极致。
一:介绍
在单纯的List当中是没有类型的限制和赋值限定的,可以随意使用,但是如果天马行空的乱用,迟早会得到类型转换失败的异常。 很多程序员觉得List<Object>的用法完全等同于List,但是在接受其他泛型赋值时就会编译出错,List<?>是一个泛型,在没有赋值之前,表示它可以接受任何类型的集合赋值,但是赋值之后就不能随便在往里添加元素了。下面的例子很好的说明了三者的区别,以List为原型展开说明:
public class List {
public static void main(String[] args) {
//第一段 泛型出现之前的集合定义方式
List a1 = new ArrayList();
a1.add(new Object());
a1.add(new Integer(11));
a1.add(new String("String"));
//第三段:把a1的引用赋值给a2,区别在于添加了泛型<Object>
List<Object> a2 = a1;
a2.add(new Object());
a2.add(new Integer(12));
a2.add(new String("String2"));
//第三段:把a1的引用赋值给a3,区别在于添加了泛型<Integer>
List<Integer> a3 = a1;
a3.add(new Integer(13));
//下方两行编译错误,不允许添加非Integer的类型值进入
a3.add(new Object());
a3.add(new String("String2"));
//第四段 : 把a1的引用赋值给a4,区别在与增加了通配符
List<?> a4 = a1;
//允许删除和清除元素
a1.remove(0);
a4.clear();
//不允许添加任何元素
a4.add(new Object());
}
}
第一段说明:在定义List之后,毫不犹豫的往里面添加三种不同类型的对象Integer,Object,String,遍历时没有问题,但是如果有一天别人在处理List中的数据时,误认为List中全是Integer类型或者String类型的数据,那么就会抛出ClassCastException异常。
第二段说明:把a1赋值给a2,a2是List<Object>类型,也可以再往里装入三种不同的对象,很多程序员认为List和List<Object>是完全相同的,至少从目前这两段看来是这样的。
第三段说明:由于泛型是在JDK5之后才出现的,考虑到向前兼容,因此历史代码有时候需要赋值给新泛型代码,从编译器角度是允许的,但是在实际的案例中有这么一个问题:
List<Integer> list = new ArrayList<Integer>(10);
list.addAll(JsonObject.getJSONArray("level"));
int amout = 0;
for(Integer t: list) {
//抛出ClassCastException异常,String can not be cast to Integer.
if(condition) {
amount = amount + t;
}
}
思考:为什么在addAll()的时候没有抛出异常,反而在遍历的时候抛出了呢?
首先我们看一下传入addAll()中的参数是一个JSONArray对象,JSONArray的定义如下:
public final class JSONArray extends AbstractJSON implements JSON, List {}
实现了List接口,是非泛型集合,可以赋值给任何泛型限制的集合。编译的时候可以通过,但是在运行的时候就会报错,因为如果List集合有了泛型约束那么在这组数据中就只能存在一种类型数据,这是一个隐藏Bug,最终导致线上故障。所以说在JDK5之后我们在传入值的时候,如果存在泛型,则要使用对象的泛型定义的集合,类或者对象作为参数传入。
补充:
如果说将a1的定义从List a1修改为List<Object> a1, 那么第三段就会编译出错。List<Object>赋值给List<Integer>是不允许的,若是反过来赋值。
List<Integer> initList = new ArrayList<Integer>(3);
initList.add(11);
List<Object> objectList = intiList;
事实上,编译也会报错,提示如下:
cannot be convered to java.util,List<java.lang.Object>
而数组是可以这样赋值的,因为它是协变的,而集合不是!
协变性:数组的协变性是一种类型系统的特性,允许将子类类型的数组赋值给父类型的数组,这种特性在某些情况下可以提高代码的灵活性和可读性,但是需要小心使用,以免造成上面的编译通过,运行时错误的异常。
第四段说明:问号在正则表达式中可以匹配任何字符,List<?>称为通配符集合。它可以接受任何类型的集合引用赋值。
集合引用赋值:只是将引用(内存地址)赋值给了另一个变量,而不是复制了集合的内容。因此对其中一个集合的修改会影响到另一个集合,因此它们实际上指向了同一个集合对象。
像上文一样。
List<?> a4 = a1;
这种情况下如果修改a4中的值,a1集合中的值也会受到影响不能添加任何元素,但可以remove和clear,List<?>一般作为参数来接收外部的集合,或者放回一个不知道的具体元素类型的集合。List<T>最大的问题是只能放置一种类型,如果随意转换类型就会出现异常。
二:总结
List集合在我们平时使用较为广泛,与Java泛型机制联合使用时,以确保集合中只存储指定类型的元素,可以提高类型的安全性,通过合理的使用泛型,可以避免类型转换错误和运行时异常。但是使用时也需要注意上面一些问题,以免造成上线的时的巨大漏洞。