解决 Java 异常 “ClassCastException: Ljava.lang.Object; cannot be cast to Ljava.lang.Integer”
1、概览
在 Java 中,数组是语言的基本组成部分,它提供了一种结构化的方式来存储相同类型的多个值。然而,在使用数组和类型转换时,我们有时会遇到意想不到的运行时异常。
当我们试图将 Object[]
数组转换为特定数组类型(如 Integer[]
)时,就会出现这样的问题。这会导致 ClassCastException
异常,这可能会让很多人感到困惑。
本文将带你了解出现这种异常的原因,从而了解 Java 数组的基本机制,并学习如何在代码中避免此类错误。
2、问题介绍
像往常一样,先通过一个例子来了解这个问题:
Integer[] convertObjectArray() {
Object[] objArray = new Object[3];
objArray[0] = 1;
objArray[1] = 2;
objArray[2] = 3;
return (Integer[]) objArray;
}
在上述方法中,我们在一个 Object[]
数组中插入了三个 int
值。由于 Object[]
数组只包含 Integer
,我们尝试将其转换为 Integer[]
数组。
在测试中调用该方法,看看会发生什么:
Exception ex = assertThrows(ClassCastException.class, () -> convertObjectArray());
LOG.error("The exception stacktrace:", ex);
可以看到,调用该方法会抛出 ClassCastException
。在输出中,还可以看到异常的详细信息:
java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.Integer; ...
at ...
at ...
...
信息非常简单。它说我们无法将 Object[]
数组转换为 Integer[]
数组,尽管数组中的所有元素都是 Integer
。接下来,让我们了解一下为什么会出现这个问题。
3、为什么会出现这种异常?
要理解为什么会发生这种情况,我们需要研究 Java 中数组的行为,特别是数组的协变性和运行时类型检查。
协变指在某些情况下,子类类型可以替代父类类型。在 Java 中,数组是协变的,这意味着子类(Integer[]
)的数组可以分配给其超类(Object[]
)的数组。
如下:
Object[] objArray = new Integer[5]; // 有效,因为 Integer[] 是 Object[] 的子类型
objArray[0] = 42; // 允许,因为 42 是 Integer
但是,这并不意味着我们可以随意将 Object[]
转换为 Integer[]
。实际数组类型仍然是 Object[]
,Java 不允许在运行时将其视为 Integer[]
。
因此,下面的代码会异常:
(Integer[]) objArray
此时,Java 会检查 objArray
是否实际上是一个 Integer[]
。由于它的实际运行时类型是 Object[]
,转换失败,导致 ClassCastException
异常。
4、解决这个问题
了解了问题出现的原因后,来看看如何解决它。
4.1、直接使用 Integer[]
避免这个问题的最好方法是从一开始就用正确的类型初始化数组。在我们的示例中,如果计划存储 Integer
值,我们可以创建一个 Integer[]
数组,而不是创建一个 Object[]
数组:
Integer[] getIntegerArray() {
Integer[] intArray = new Integer[3];
intArray[0] = 1;
intArray[1] = 2;
intArray[2] = 3;
return intArray;
}
测试如下:
assertArrayEquals(new Integer[] { 1, 2, 3 }, getIntegerArray());
用所需的类型初始化数组可以节省不必要的转换。但是,我们经常会从其他库或 API 接收 Object[]
数组。在这种情况下,我们无法从一开始就初始化数组。接下来,让我们看看如何将 Object[]
数组转换为 Integer[]
数组。
4.2、基于 Stream 的转换
我们可以利用 Stream API 将 Object[]
转换为 Integer[]
:
Integer[] objArrayToIntArrayByStream() {
Object[] objArray = new Object[] { 1, 2, 3 };
Integer[] intArray = Stream.of(objArray).toArray(Integer[]::new);
return intArray;
}
在本例中,toArray(Integer[]::new)
将数据流中的元素收集到一个新的 Integer[]
数组中。
测试:
assertArrayEquals(new Integer[] { 1, 2, 3 }, objArrayToIntArrayByStream());
这种方法可以完成任务,但必须注意类型安全。需要确保 Object[]
中的所有元素都是 Integer
实例。否则,在将任何非 Integer
元素转换为 Integer
时,会抛出 ClassCastException
。
4.3、基于循环的转换
另外,我们也可以通过 for
循环轻松地将 Object[]
数组转换为 Integer[]
数组:
Integer[] objArrayToIntArray() {
Object[] objArray = new Object[]{ 1, 2, 3 };
Integer[] intArray = new Integer[objArray.length];
for (int i = 0; i < objArray.length; i++) {
intArray[i] = (Integer) objArray[i];
}
return intArray;
}
该方法通过读取 Object[]
数组中的每个元素,并在循环中将其添加到目标 Integer[]
数组中,从而解决问题:
assertArrayEquals(new Integer[] { 1, 2, 3 }, objArrayToIntArray());
同样,如果 Object[]
包含非 Integer
元素,就会抛出 ClassCastException
。
4.4、泛型方法
我们可以将基于循环的转换方法扩展为泛型方法,这样就可以将 Object[]
数组转换为任何类型的数组(T[]
)。
<T> T[] convertFromObjectArray(Class<T> clazz, Object[] objArray) {
T[] targetArray = (T[]) Array.newInstance(clazz, objArray.length);
for (int i = 0; i < objArray.length; i++) {
if (clazz.isInstance(objArray[i])) {
targetArray[i] = clazz.cast(objArray[i]);
} else {
throw new ClassCastException("Element #" + i + ": Cannot cast " + objArray[i].getClass()
.getName() + " to " + clazz.getName());
}
}
return targetArray;
}
在此实现中,我们首先初始化了一个泛型数组。然后,在转换之前明确检查每个元素,并抛出一个定义明确的带有明确信息的 ClassCastException
。
现在,可以使用该方法将 Object[]
转换为不同类型的数组:
assertArrayEquals(new Integer[] { 1, 2, 3 }, convertFromObjectArray(Integer.class, new Object[] { 1, 2, 3 }));
assertArrayEquals(new String[] { "I'm Kai", "I'm Liam", "I'm Kevin" },
convertFromObjectArray(String.class, new Object[] { "I'm Kai", "I'm Liam", "I'm Kevin" }));
当然,如果传入的 Object[]
数组包含其他类型的元素,则会抛出 ClassCastException
异常:
Exception ex = assertThrows(ClassCastException.class, () -> convertFromObjectArray(String.class, new Object[] { "I'm Kai", Instant.now(), "I'm Kevin" }));
assertEquals("Element #1: Cannot cast java.time.Instant to java.lang.String", ex.getMessage());
如上,ClassCastException
包含有意义的信息,有助于调试和确定是哪个特定元素导致了异常。
5、总结
本文介绍了在 Java 中将 Object[]
转换为 Integer[]
时抛出 ClassCastException
异常的原因,以及几种解决办法。
Ref:https://www.baeldung.com/java-fix-classcastexception-ljava-lang-object