簡體   English   中英

Java 轉換接口到類

[英]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 的編譯時合法性的詳細規則- 請注意,它們非常復雜且難以理解。

有趣的規則是:

  • 如果S接口類型:
    • 如果T是非最終類型(第 8.1.1 節),則如果存在 T 的超類型 X 和 S 的超類型 Y,則 X 和 Y 都是可證明不同的參數化類型,並且X 和 Y 相同,發生編譯時錯誤。 否則,轉換在編譯時總是合法的(因為即使 T 沒有實現 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;
    }    
}

現在可以在運行時將SerializableA ,這表明在這些情況下,最好由運行時決定我們是否可以轉換。

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 ....

它不能知道,因為編譯時類型serializableSerializable

為了說明,請考慮:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM