解決 ClassCastException:Ljava.lang.Object;無法轉換為 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;
}
我們在上面的方法中將三個int
值插入到Object[]
陣列中。由於Object[]
數組 僅包含整數,我們嘗試將其轉換為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]; // Valid, because Integer[] is a subtype of Object[]
objArray[0] = 42; // Allowed, since 42 is an 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 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
Integer.
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
。我們也透過範例探討了解決該問題的幾種方法。
與往常一樣,範例的完整原始程式碼可在 GitHub 上找到。