解决 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