Java不允许创建泛型数组的原因
上面链接中的Xuan Luo
的回答很好. 简单来说就是Java数组必须有一个具体的类型信息, 但是因为泛型擦除, 所以无法提供具体的类型信息给数组, 因而无法创建泛型数组. 而容器其实泛型擦除后根本没类型信息了, 所以全部的List<AnyType>
最后都是List
. 看下面这个例子:
public static void testTypeInformation(){
String[] strings1 = new String[0];
String[] strings2 = new String[0];
Integer[] integers = new Integer[0];
System.out.println(String.format("strings1.class == strings2.class: %s", strings1.getClass() == strings2.getClass()));
// 直接这样写编译器直接给出了错误
// strings1.getClass() == integers.getClass()
// System.out.println(String.format("strings1.class == integers.class: %s", strings1.getClass() == integers.getClass()));
Class<?> stringArrayClass = strings1.getClass();
Class<?> integerArrayClass = integers.getClass();
System.out.println(String.format("strings1.class == integers.class: %s", stringArrayClass == integerArrayClass));
// 再看容器类
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
System.out.println(String.format("stringList.class == integerList.class: %s", stringList.getClass() == integerList.getClass()));
}
输出:
strings1.class == strings2.class: true
strings1.class == integers.class: false
stringList.class == integerList.class: true
可以看到数组
是确确实实有具体类型, 而泛型容器最终都是容器class本身, 没有新的类生成.
一些创建泛型数组的方法
调用反射包的方法
/**
* 该方法可以安全地创建泛型数组
* 调用反射包里的Array.newInstance(type, length)方法.
* 具体的实现是靠native方法.
* @param type 元素类型
* @param length 长度
* @param <T> 类型
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T[] createArray(Class<T> type, int length){
return (T[]) Array.newInstance(type, length);
}
强制类型转换 (错误方法)
/**
* 编译不会报错, 但是运行时
* 只要T不是Object, 那一定会报错
* 因为Java对于数组的定义, 是必须要有具体类型的
* 虽然 String 可以转换为 Object, 但是Object不一定能转换成String
* 一样的道理 String[] 可以转换为 Object[], 但是Object[]不一定能
* 转换成String[], 另外String[]转换为Object[]是合法的, 但是千万
* 不要这样做, 很可能会出错, 因为转换为Object[]就能存任意引用了,
* 但是底层仍然是String[], 如果存的不是String, 那么就会报错.
* @param len 长度
* @param <T> 类型参数, 可由类型推导推导得知, 也可以显式
* 指定, ClassName.<Type>function()
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T[] createArray(int len){
return (T[]) new Object[len];
}
测试:
// ClassCastException
// Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
// 使用了类型推导
try {
Integer[] integers = createArray(10);
} catch (ClassCastException e){
System.out.println("exception: " + e.getMessage());
}
// 也可以这样, 显式指定类型
// Integer[] integerArray = GenericArray.<Integer>createArray(10);
因为数组的实际类型是Object[]
, 是无法转型为Integer[]
的.
限制访问的Object[]方法
/**
* 使用底层Object[]来存储元素的折中做法
* 不能暴露Object[]出去, 否则无法保证
* 往里面加的类型是否安全
* @param <T>
*/
public static class MyArray<T>{
// 存放实际的数据
// 因为所有对象都能向上转型为Object
// 所有用Object[]来存各类对象是没问题的
private Object[] innerArray;
public MyArray(int len){
innerArray = new Object[len];
}
@SuppressWarnings("unchecked")
public T get(int index){
// 只要set方法中传入的确实是T类型的对象
// 那么这里一定是安全的
return (T) innerArray[index];
}
public void set(int index, T item){
innerArray[index] = item;
}
@Override
public String toString() {
return Arrays.toString(innerArray);
}
}
测试:
MyArray<String> stringArray = new MyArray<>(3);
stringArray.set(0, "The");
stringArray.set(1, "safe");
stringArray.set(2, "generic array");
System.out.println(stringArray);
输出:
[The, safe, generic array]