[英]ClassCastException using Generics and Varargs
我最近刚接触Java泛型,并想复制可与Array一起使用的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;
我没有在任何地方将Integer Array转换回Object Array,也无法真正弄清楚出了什么问题,因为在那一行中,该数组仍然是Integer Array,并且永远不会进入匿名方法。
抱歉,如果这是我应该轻松找到解决方案的问题,我尝试过但没有发现。 另外,如果您不赞成,请告诉我原因。
泛型和数组,以及泛型和varargs在Java中混合不好。 无论如何,数组在Java中并不常用。 在Java中,使用List<Integer>
比使用Integer[]
更常见,这样可以避免您将要听到的所有麻烦。 因此,对于实际程序,只需不要将泛型和数组或泛型和varargs一起使用,就可以了。
请勿阅读其他内容! 只需使用List<T>
而不是T[]
并且不要将varags与泛型一起使用!
您已被警告。
让我们对问题函数进行一些解压缩。 我们可以将其更明确地重写为:
public <E> E[] map(E[] array, Function<E> func) {
E e = array[0];
E res = func.call(e);
array[0] = res;
return array;
}
如果在调试器中逐步执行此功能,则将看到E res = func.call(e);
引发了异常,但我们甚至从未到达函数调用的主体。
要了解原因,您必须了解数组,泛型和varargs如何一起工作(或不工作)。 call
被声明为public T call(T... vals)
。 在Java中,语法糖意味着两件事:
call
实际上具有T call(T[] vals)
类型T call(T[] vals)
call(T t1, T t2, /*etc*/)
站点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[]
创建的以支持该值。 另一方面,泛型没有运行时组件。 您可能听说过Erase ,这就是指:所有的泛型内容在编译时都会检查,但会从程序中删除,并且完全不会影响运行时。
那么,如果尝试创建某种通用类型的数组(如new T[]{}
什么? 它不会编译,因为该数组需要知道运行时的T
才能正常工作,但是Java不知道运行时的T
是什么。 因此,编译器根本不允许您构建其中之一。
但是存在漏洞。 如果使用泛型类型调用varargs函数,则Java允许程序进行编译。 回想一下varargs方法的callsite创建了一个新数组-那么它赋予该数组什么类型呢? 在这种情况下,由于Object
是T
的擦除,因此将其仅创建为Object[]
。
在某些情况下,这可以正常工作,但是您的程序不是其中之一。 在您的程序中, func
承担的价值是
public Integer call(Integer... vals) {
return vals[0] += 2;
}
身体并不重要; 您可以将其替换为return null;
该程序的行为将相同。 重要的是它需要Integer...
,而不是Object...
或T...
或类似的东西。 请记住, Integer...
是语法糖,编译后的确需要Integer[]
。 因此,当您在数组上调用此方法时,首先要做的是将其Integer[]
为Integer[]
。
就是问题所在:在调用站点,我们只知道该函数使用了T...
,所以我们用新创建的Object[]
对其进行了编译(该Object[]
在运行时恰好用整数填充,但是编译器没有不知道这一点)。 但是被调用方需要一个Integer[]
,并且由于上述原因,您不能将作为new Object[]{}
构造的数组向下转换为Integer[]
。 尝试时,您会收到java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
这正是您的程序产生的结果。
没有理由尝试了解上面发生错误的所有详细信息。 相反,这是我希望您带走的东西:
List
于数组。 集合是Java的方式。 希望能有所帮助。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.