我有以下泛型方法,返回一个通用数组:
public static <T> T[] genericMethod1(List<T> input) {
T[] res = (T[]) new Object[input.size()];
int i = 0;
for (T t : input) {
res[i] = t;
i++;
}
return res;
}
public static <T> T genericMethod2(List<T> input) {
return input.get(0);
}
但后来当我尝试使用以下结果获取结果数组时:
LinkedList<Integer> list = new LinkedList<Integer>();
list.addFirst(1);
list.addFirst(1);
Integer[] i = (Integer[]) genericMethod1(list); // 1) Runtime error
Integer j = genericMethod2(list); // 2) works
对于案例1,我总是在运行时遇到错误:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
任何人都可以解释为什么以及如何正确返回通用数组?谢谢。
以下是我的理解,如果我错了,请纠正我。
正如Tim所提到的,类型擦除发生在编译时,因此在字节码中,每个T对象只是类型Object,同时,编译器会将对象的类型转换为“正确”。
假设T是一个整数,其中声明了T,它是对象。对于它所引用的位置,它是T的类型转换(隐式)。
除了声明T []数组,它是Object [],并且引用数组的地方,它仍然是Object []。没有隐式转换为T []。
你所看到的解释是由于称为类型擦除的东西。以下是编译器执行类型擦除后genericMethod()
的样子:
public static Object[] genericMethod(List input) {
Object[] res = new Object[input.size()];
int i = 0;
for (Object t : input) {
res[i] = t;
i++;
}
return res;
}
换句话说,此方法将返回Object
类型的数组。没有办法将Object[]
施放到Integer[]
,因为它们不是同一类型。如果您希望您的方法能够动态返回所需的类型,那么您可以使用Array.newInstance()
。这还需要传入您想要的数组类型作为输入参数:
public static <T> T[] genericMethod(Class<T> clazz, List<T> input) {
@SuppressWarnings("unchecked")
T[] res = (T[]) Array.newInstance(clazz, input.size());
int i = 0;
for (T t : input) {
res[i] = t;
i++;
}
return res;
}
现在,您的代码段将无错误地运行:
LinkedList<Integer> list = new LinkedList<Integer>();
Integer[] i = genericMethod(Integer.class, list);
更新:
第二种方法genericMethod2()
在类型擦除后将如下所示:
public static Object genericMethod2(List input) {
return input.get(0);
}
它将返回输入列表的第一个元素,强制转换为Object
。以下是您对该方法的使用方法:
Integer j = genericMethod2(list);
编译器将尝试将genericMethod2()
的输出强制转换为Integer
:
Integer j = (Integer)genericMethod2(list);
这个演员是合法的,因为每个Integer
也是一个Object
,而且它在这里成功,因为你传递了Integer
的集合。第二种方法与您为我们突出显示的第一种方法不同。
在调用方法时,genericMethod
假设它返回整数数组,这是不正确的。它实际上在运行时返回Object类型的数组。
List<Integer> input = new ArrayList<Integer>();
input.add(1);
Object[] output = genericMethod(input);
for(Object obj : output){
System.out.println("Value= "+ (Integer)obj);
}
所以我们需要转换数组的各个内容。
一般指导原则是我们不应该在Java中混合使用ARRAY和GENERICS。
更新:
来自Effective Java的参考:
在摘要中,数组和泛型具有非常不同的类型规则。数组是协变的和具体化的;泛型是不变的和擦除的。作为一个结果,数组提供运行时类型安全性,但不提供编译时类型安全性,反之亦然。一般来说,数组和泛型不能很好地混合。如果你发现自己混合它们并得到编译时错误或警告,你的第一个冲动应该是用列表替换数组。
另一种方法是在java.util.ArrayList.toArray(T[])
中这样做。您将Array的类型传递给该Method,如果它足够大,它将被重用,否则会生成一个新的Array。
例:
List<Integer> intList = new ArrayList<>();
intList.add(Integer.valueOf(1));
intList.add(Integer.valueOf(2));
intList.add(Integer.valueOf(3));
Integer[] array = intList.toArray(new Integer[] {});
System.out.println(Arrays.toString(array));//Will Print [1, 2, 3]
ArrayList.toArray(T[])
的实施见here。