泛型容器类的固有风险
先看一个例子(例1):
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
List list1 = new ArrayList<String>();
List list2 = new ArrayList<Integer>();
/**
*这里赋值并不会报错,因为类型安全性检查是针对引用的,list1和list2的引用类型都是ArrayList类型
*上面两句的写法本质上等同于:List list1 = new ArrayList();这种写法
*/
list1 = list2;
//下面的add也不会报错,这是因为list1的编译时类型还是List,但是当访问元素时发生了类型不匹配 的错误
list1.add(1);
list1.add(3);
list1.add(3.14);
//这里编译器会提示类型错误:返回的是Object类型(类型擦除),接收的是int类型
int s = list1.get(0);
System.out.println(list1.get(0));
}
}
上面这种写法显然会发生类型错误。
下面这种写法是较为稳妥的(例2):
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
//在定义list1,list2的时候就确定编译时类型,运行时类型可以由编译器自行推断(显式声明也可)
List<Integer> list1 = new ArrayList();
List<Integer> list2 = new ArrayList();
list1 = list2;
//这里添加 list1.add("1"); 这种不同类型的元素会编译器会报告类型匹配错误
list1.add(1);
//可以正确赋值
int a = list1.get(1);
//输出1
System.out.println(a);
}
}
为什么说上一种写法是 较为稳妥的呢?
因为虽然避免了因泛型中类型不同而产生的编译时错误,但是运行时类型错误还是存在的(主要是因为多态性的运行时决定实际类型的机制导致的)。
再看下面的例子(例3):
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
//ArrayList实现了List接口
List<Integer> list1 = new ArrayList();
ArrayList list2 = new ArrayList();
list2.add("1");
//ArrayList实现了List接口,所以赋值操作编译器不会报错,但类型错误已产生(以一种相对隐蔽的方式)
list1 = list2;
//list1编译期类型是Integer,所以下面的赋值不报错
int a = list1.get(0);
System.out.println(a);
}
}
运行程序后产生的错误信息如下:
E:\JDK8\bin\java.exe ...
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at com.youkeda.test.Test.main(Test.java:16)
Process finished with exit code 1
相较于例1,例3的错误是无法被编译器发现的,这是因为编译时的类型检查没有问题,然而运行时的实际类型是ArrayList(多态性)。
综上所述,泛型容器类赋值风险仅靠编译器和语言约束是不可避免的,要靠程序员编写程序时小心出现此类错误。