使用泛型和Varargs的java ClassCastException
我最近刚接触Java泛型,并希望复制可用于数组的JavaScript映射函数。现在我无法找出代码中的错误:
public class Test {
public interface Function<T> {
public T call(T... vals);
}
public <E> E[] map(E[] array, Function<E> func) {
for (int i = 0; i < array.length; i++) {
array[i] = func.call(array[i]); <--- Exception
}
return array;
}
public Test() {
Integer foo[] = {3, 3, 4, 9};
foo = map(foo, new Function<Integer>() {
@Override
public Integer call(Integer... vals) {
return vals[0] += 2;
}
});
for (Integer l : foo) {
System.out.println(l);
}
}
public static void main(String[] args) {
new Test();
}
}
我在指定行遇到ClassCastException:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
我没有将整数数组强制转换回任何地方的对象数组,我也无法真正找出哪里出了问题,因为在这一行中,数组仍然是整数数组,而且它永远不会进入匿名方法
抱歉,如果这是一个我应该很容易找到解决方案的问题,我尝试了,但没有找到。如果你投了反对票,请告诉我原因
# 1 楼答案
实际答案
泛型和数组,以及泛型和varargs在Java中不能很好地混合。无论如何,数组在Java中使用不多;在Java中,使用
List<Integer>
比使用Integer[]
更为常见,这样做可以避免您将听到的所有痛苦。所以对于实际的程序,只要不要同时使用泛型和数组,或者泛型和varargs,就可以了不要再读下去了!只需使用
List<T>
而不是T[]
,并且不要将varags与泛型一起使用强>你已经被警告过了
技术答案
让我们稍微解包一下问题函数。我们可以更明确地将其改写为:
如果您在调试器中单步执行此函数,您将看到
E res = func.call(e);
行抛出异常,但我们甚至从未到达函数调用的主体要了解原因,您必须了解数组、泛型和vararg如何一起工作(或不一起工作)
call
被声明为public T call(T... vals)
。在Java中,这是一种语法糖,它意味着两件事:call
实际上具有类型T call(T[] vals)
call(T t1, T t2, /*etc*/)
都应该转换为call(new T[]{t1, t2, /*etc*/})
,在调用方法之前在调用方的代码中隐式构建一个数组李>如果在
call
的声明中没有使用像T
这样的类型变量,而是使用像Integer
之类的普通类型,那么故事就到此结束了。但由于它是一个类型变量,我们必须更多地了解泛型和数组之间的相互作用泛型和数组
Java中的数组包含一些关于它们是什么类型的运行时信息:例如,当您说
new Integer[]{7}
时,数组对象本身会记住它是作为Integer
数组创建的。这有几个原因,都与铸造有关:Integer[]
,如果您试图做一些像((Object[]) myIntegerArray)["not a number!"]
这样的鬼鬼祟祟的事情,Java会抛出一个运行时错误,否则会使您陷入一种奇怪的情况,整数数组包含一个字符串;要做到这一点,它需要在运行时检查放入数组中的每个值是否与Integer
兼容Integer[]
强制转换到Object[]
并返回到Integer[]
,但不能返回到,比如说,String[]
,如果您尝试,您将在运行时在向下转换时收到类强制转换异常。Java需要知道该值是作为Integer[]
创建的,以支持这一点李>另一方面,泛型没有运行时组件。您可能听说过擦除,这就是它所指的:所有泛型内容都在编译时检查,但从程序中删除,并且根本不影响运行时
那么,如果您尝试创建某种泛型类型的数组,比如
new T[]{}
,会发生什么呢?它不会编译,因为数组需要知道运行时T
是什么才能工作,但Java不知道运行时T
是什么。所以编译器根本不允许您构建其中的一个但是有一个漏洞。如果使用泛型类型调用varargs函数,Java允许程序编译。回想一下,varargs方法的callsite创建了一个新数组,但是它给了该数组什么类型呢?在本例中,它将创建一个
Object[]
,因为Object
是对T
的擦除在某些情况下,这将很好地工作,但您的程序不是其中之一。在您的程序中,
func
采用的值为身体并不重要;您可以用
return null;
替换它,程序的行为将相同。重要的是它需要Integer...
,而不是Object...
或T...
之类的东西。记住Integer...
是语法糖,编译后确实需要Integer[]
。因此,当您对数组调用此方法时,它要做的第一件事是将其强制转换为Integer[]
这就是问题所在:在呼叫中心,我们所知道的这个函数需要
T...
,所以我们用一个新创建的Object[]
来编译它(在运行时它碰巧被一个整数填充,但编译器静态地不知道)。但是被调用方需要一个Integer[]
,由于上面讨论的原因,您不能将构造为new Object[]{}
的数组向下转换为Integer[]
。当您尝试时,您会得到java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
,这正是您的程序所产生的结果这个故事的寓意
没有理由试图了解上述问题的所有细节。相反,以下是我希望你能带走的东西:
List
这样的Java集合而不是数组集合是Java的方式李>希望有帮助