gpt4 book ai didi

java - 使用泛型和Varargs的ClassCastException

转载 作者:行者123 更新时间:2023-11-30 07:46:51 31 4
gpt4 key购买 nike

我最近刚接触Java泛型,并想复制可用于Arrays的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)
任何调用站点 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[]创建的以支持该值。


另一方面,泛型没有运行时组件。您可能听说过擦除,这是指擦除:所有泛型内容在编译时都会检查,但是会从程序中删除,并且完全不会影响运行时。

那么,如果您尝试制作一些通用类型的数组(如 new T[]{})会怎样?它不会编译,因为该数组需要在运行时知道 T是什么才能工作,但是Java不知道在运行时 T是什么。因此,编译器根本不允许您构建其中之一。

但是存在漏洞。如果使用泛型类型调用varargs函数,则Java允许程序进行编译。回想一下varargs方法的callsite创建了一个新数组-那么它赋予该数组什么类型呢?在这种情况下,由于 Object[]Object的擦除,因此将其仅创建为 T

在某些情况下,这可以正常工作,但是您的程序不是其中之一。在您的程序中, func的值是

public Integer call(Integer... vals) {
return vals[0] += 2;
}


身体并不重要;您可以将其替换为 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;,它正是程序产生的结果。

这个故事的主旨

没有理由尝试了解上面发生错误的所有详细信息。相反,这是我希望您带走的东西:


首选Java集合(例如 List)而不是数组。集合是Java的方式。
可变参数和泛型不能混合使用。将它们混合在一起时,您可以轻松地走入此类陷阱。不要打开这些门。


希望能有所帮助。

关于java - 使用泛型和Varargs的ClassCastException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50333155/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com