我最近刚进入Java Generics并希望复制可以与Arrays一起使用的JavaScript map函数。现在我无法弄清楚我的代码中出了什么问题:
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数组转换回任何地方的Object Array,我无法弄清楚出现了什么问题,因为在该行中数组仍然是一个整数数组,它永远不会进入匿名方法。
很抱歉,如果这是一个我应该很容易找到解决方案的问题,我试过但我找不到。另外,如果你投票,请告诉我原因。
答案 0 :(得分:3)
泛型和数组,以及泛型和变量,在Java中不能很好地混合。无论如何,Java中的数组使用不多;在Java中,使用List<Integer>
而不是Integer[]
更为常见,这样做可以避免您将要听到的所有痛苦。因此对于实际程序,只是不要将泛型和数组,或泛型和变量一起使用,你会没事的。
请勿阅读此内容!只需使用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);
抛出了异常,但我们甚至都没有进入函数调用的主体。
要理解原因,您必须了解数组,泛型和变量如何一起工作(或不工作)。 call
被声明为public T call(T... vals)
。在Java中,语法糖意味着两件事:
call
实际上具有T call(T[] vals)
call(T t1, T t2, /*etc*/)
都应转换为call(new T[]{t1, t2, /*etc*/})
,在调用方法之前隐式地在调用者的代码中构建数组。如果不是在T
的声明中使用类似call
的类型变量,而是使用像Integer
这样的普通类型,那么这将是结束故事。但由于它是一个类型变量,我们必须更多地了解泛型和数组之间的相互作用。
Java中的数组带有一些关于它们的类型的运行时信息:例如,当您说new Integer[]{7}
时,数组对象本身会记住它是作为Integer
数组创建的。这有两个原因,都与铸造有关:
Integer[]
,如果您尝试像((Object[]) myIntegerArray)["not a number!"]
那样偷偷摸摸地执行某些操作,那么Java会抛出运行时错误,否则会让您陷入奇怪的情况,即您的整数数组包含字符串;要做到这一点,它需要在运行时检查您放入数组中的每个值是否与Integer
Integer[]
投降至Object[]
并返回Integer[]
,但不要退回至String[]
- 如果您尝试将获得运行时向下转换的类转换异常。 Java需要知道该值是作为Integer[]
创建的,以支持它。另一方面,泛型没有运行时组件。您可能听说过 erasure ,这就是所指的:所有泛型内容在编译时都会被检查,但会从程序中删除,并且根本不会影响运行时。
那么如果你试图创建一些泛型类型的数组会发生什么呢,比如new T[]{}
?它不会编译,因为数组需要知道运行时T
是什么,但是Java不会知道运行时T
。所以编译器根本不允许你构建其中的一个。
但是存在漏洞。如果使用泛型类型调用varargs函数,Java允许程序进行编译。回想一下,varargs方法的调用点创建了一个新数组 - 所以它给那个数组提供了什么类型?在这种情况下,它只会将其创建为Object[]
,因为Object
是T
的删除。
在某些情况下,这可以正常工作,但您的程序不是其中之一。在您的程序中,func
所使用的值是
public Integer call(Integer... vals) {
return vals[0] += 2;
}
身体并不重要;你可以用return null;
替换它,程序的行为会相同。重要的是它需要Integer...
,而不是Object...
或T...
或类似的东西。请记住,Integer...
是语法糖,编译后它真的需要Integer[]
。因此,当您在数组上调用此方法时,首先要做的是将其强制转换为Integer[]
。
这就是问题所在:在callsite上,我们所知道的是这个函数花了T...
,所以我们用新创建的Object[]
编译它(恰好填充了一个整数运行时,但编译器并不知道静态)。但是被调用者需要一个Integer[]
,并且由于上面讨论的原因,你不能将构造为new Object[]{}
的数组向下转换为Integer[]
。当你尝试时,你会得到java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
,这正是你的程序产生的。
没有理由尝试了解上述问题的所有细节。相反,这是我希望你带走的东西:
List
到数组。集合是Java方式。希望有所帮助。