[英]Java Casting Interface to Class
public class InterfaceCasting {
private static class A{}
public static void main(String[] args) {
A a = new A();
Serializable serializable = new Serializable(){};
a = (A)serializable;
}
}
編譯成功但運行時異常
Exception in thread "main" java.lang.ClassCastException: InterfaceCasting$1 cannot be cast to InterfaceCasting$A
為什么編譯成功? 編譯器一定知道serialiazable不是A?
正如您所指出的,這將編譯:
interface MyInterface {}
class A {}
public class InterfaceCasting {
public static void main(String[] args) {
MyInterface myObject = new MyInterface() {};
A a = (A) myObject;
}
}
但是,這不會編譯:
interface MyInterface {}
class A {}
public class InterfaceCasting {
public static void main(String[] args) {
A a = (A) new MyInterface() {}; // javac says: "inconvertible types!"
}
}
那么,這里發生了什么? 有什么不同?
那么,既然MyInterface
是一個簡單的接口,它很可能是由延伸的一類,在這種情況下,從投實現MyInterface
,以A
是合法的。
例如,這段代碼將在所有執行的 50% 中成功,並說明編譯器需要解決可能無法確定的問題,以便在編譯時始終“檢測”非法強制轉換。
interface MyInterface {}
class A {}
class B extends A implements MyInterface {}
public class InterfaceCasting {
public static void main(String[] args) {
MyInterface myObject = new MyInterface() {};
if (java.lang.Math.random() > 0.5)
myObject = new B();
A a = (A) myObject;
}
}
Java 語言規范指出:
某些強制轉換在編譯時可能被證明是不正確的; 這種強制轉換會導致編譯時錯誤。
稍后在節目中將編譯時引用類型 S 的值轉換為編譯時引用類型 T 的編譯時合法性的詳細規則- 請注意,它們非常復雜且難以理解。
有趣的規則是:
在您的示例中,很明顯,演員陣容是非法的。 但是考慮一下這個細微的變化:
public class InterfaceCasting {
private static class A{}
private static class B extends A implements Serializable{}
public static void main(String[] args) {
A a = new A();
Serializable serializable = new B(){};
a = (A)serializable;
}
}
現在可以在運行時將Serializable
為A
,這表明在這些情況下,最好由運行時決定我們是否可以轉換。
Serializable serializable;
a = (A)serializable;
至於編譯器,變量 serializable 可以包含任何實現Serializable
對象,其中包括A
子類。 所以它假設您知道變量確實包含一個A
對象並允許該行。
編譯器不夠聰明,無法追蹤可serializable
的起源並意識到它永遠不可能是A
類型。 它實際上只評估該行:
a = (A)serializable;
並看到serializable
是一個Serializable
類型的引用,但它可能引用一個也是A
類型的類。 可serializable
引用的實際類直到運行時才知道。
在這種微不足道的情況下,我們知道此轉換永遠不會成功,但通常這會留為運行時問題,因為可能導致轉換的不同代碼路徑(理論上)是無限的。
如果你想在運行時避免這個問題,你可以測試它..
if (serializable instanceof A) {
a = (A)serializable;
} else ....
它不能知道,因為編譯時類型serializable
是Serializable
。
為了說明,請考慮:
private static class A{}
private static class B implements Serializable {}
Serializable serializable = new B();
A a = (A)serializable;
這與您的問題完全一樣,它可以編譯。
private static class A{}
private static class B implements Serializable {}
B b = new B();
A a = (A)b;
這不會編譯,因為b
不是A
。
雖然我不知道正確答案,但出於多種原因,將接口轉換為類通常不是一個好主意。
a) 一個接口定義了一個契約,它保證了行為。 一個類可能定義的不僅僅是這個契約,其他方法的使用可能會產生意想不到的副作用並破壞 API。 例如,當一個方法被傳遞一個列表並且你發現傳遞的對象實際上是一個 LinkedList 並且你轉換它並使用它也定義的基於隊列的方法時,你正在破壞 API。
b) 此外,具有接口的對象在運行時可能不是“真實”對象,而可能是由諸如 Spring 或 EJB 之類的庫圍繞原始對象創建的服務代理。 在這些情況下,您的演員表將失敗。
如果您絕對必須強制轉換,請不要在沒有 instanceof 檢查的情況下執行此操作:
if(myServiceObject instanceof MyServiceObjectImpl){
MyServiceObjectImpl impl = (MyServiceObjectImpl) myServiceObject;
}
將編譯時引用類型 S 的值強制轉換為編譯時引用類型 T 的編譯時合法性的詳細規則如下:
[...]
如果 S 是接口類型:
- 如果 T 是數組類型,[...]。
- 如果 T 是非最終類型(第 8.1.1 節),則如果存在 T 的超類型 X 和 S 的超類型 Y,則 X 和 Y 都是可證明不同的參數化類型,並且擦除X 和 Y 相同,則發生編譯時錯誤。 否則,轉換在編譯時總是合法的(因為即使 T 沒有實現 S,T 的子類也可能) 。
來源 :
JLS:轉換和促銷
Serializable
不是A
,所以它拋出ClassCastException
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.