Java不能创建泛型数组的解决方案
问题提出:
在java中是”不能创建一个确切的泛型类型的数组”的。
也就是说下面的这个例子是不可以的:
List<String>[] ls = new ArrayList<String>[10];
而使用通配符创建泛型数组是可以的,如下面这个例子:
List<?>[] ls = new ArrayList<?>[10];
这样也是可以的:
List<String>[] ls = new ArrayList[10];
下面使用Sun的一篇文档的一个例子来说明这个问题:
List<String>[] lsa = new List<String>[10]; // Not really allowed.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Unsound, but passes run time store check
String s = lsa[1].get(0); // Run-time error: ClassCastException.
这种情况下,由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList而不会出现异常,但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明,上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。
而对泛型数组的声明进行限制,对于这样的情况,可以在编译期提示代码有类型安全问题,比没有任何提示要强很多。
解决方案
1. 通配符
The Java™ Tutorials: Generics给出的解决方案如下:
List<?>[] lsa = new List<?>[10]; //1
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
// Correct.
lsa[1] = li;
// Run time error, but cast is explicit.
Integer s = (Integer) lsa[1].get(0); //2
借助于无限定通配符却可以,? 代表未知类型,所以它涉及的操作都基本上与类型无关,因此 jvm 不需要针对它对类型作判断,因此它能编译通过,但是,只提供了数组中的元素因为通配符原因,它只能读,不能写。比如,上面的 lsa 这个局部变量,它只能进行 get() 操作,不能进行 add() 操作。当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。但是可以通过li变量来进行add() 操作。
综上:数组的类型不可以是类型变量,除非是采用通配符的方式。因为对于通配符的方式,最后取出数据是要做显式的类型转换的。
2. 反射
使用java.util.reflect.Array
,可以不使用通配符,而达到泛型数组的效果:
List<String>[] lsa = (List<String>[])Array.newInstance(ArrayList.class, 4); //1
Object o = lsa;
Object[] oa = (Object[]) o;
List<String> li = new ArrayList<String>();
li.add("3");
/ Correct.
oa[1] = li;
// Run time error, but cast is explicit.
String s = lsa[1].get(0); //2
System.out.println(s);
可以看到,我们利用了Array.newInstance()
生成了泛型数组,这里没有使用任何通配符,在第2处也没有做显式的类型转换,但是在第1处,仍然存在显式类型转换。
3.3 总结
要想使用泛型数组
,要求程序员必须执行一次显示的类型转换,也就是将类型检查的问题从编译器交给了程序员。实际上Java的设计者正是此意,在(List<String>[])Array.newInstance(ArrayList.class, 4)
处会有一个unchecked warning
,正是编译器在提醒程序员:这个地方,我不会帮你做类型检查,你要自己小心!
。