簡體   English   中英

將接口轉換為 class 是如何工作的?

[英]How does this casting of an interface to a class work?

IY iy = new D();
C c1 = (C) iy; // What is going on here...
c1.doOther();  // and here?

public interface IX {
    void doIt();
}

public interface IY {
    void doOther();
}

public class A {
    public void doIt(double d) {
        System.out.println("Doit A " + d);
    }
}

public class B extends A implements IX {
    public void doIt() {
        System.out.println("Doit B");
    }

    public void doIt(int i) {
        System.out.println("Doit B " + i);
    }
}

public class C extends A implements IY {
    public void doIt() {
        System.out.println("Doit C");
    }

    public void doOther() {
        System.out.println("DoOther " + this.getClass().getSimpleName());
    }
}

public class D extends C {
    public void doIt() {
        System.out.println("Doit D");
    }
}

打印出DoOther D


為什么會這樣? 我正在學習 Java 類型系統,我非常困惑。

每一步到底發生了什么? 我會盡力說明我對正在發生的事情的理解。

IY iy = new D()編譯並運行良好,因為D繼承自C繼承自IY 由於傳遞性規則(D <: C <: IY => D <: IY) DIY的子類,它可以將D-object存儲到IY類型的變量中,因為DIY的類型。

C c1 = (C) iy ,這就是我感到困惑的地方。 據我了解,我們正在將D-object轉換為C-object 還是發生了其他事情? 我是否可以將IY類型(接口)的變量轉換為C類型? 類型發生了變化,但變量的值(它是對D-object的引用)在iyc1之間保持不變?

這是有效的,因為CD來說是超級的。 現在,如果我正確理解了這一點,這是一件危險的事情,因為它不會總是運行,因為不能保證CD有關系,因此它可能在運行時失敗並給出ClassCastException異常,這個對嗎?

但是c1.doOther()的結果讓我感到困惑。 如果c1是一個C-object並且我們執行c1.doOther() ,那么C-object c1不是調用它自己的實例方法doOther()嗎? 如果是這種情況,那么 this.getClass this.getClass().getSimpleName()行應該返回DoOther C但它返回DoOther D就好像它是調用其超級方法的D-object ,這似乎是實際情況?

這里到底發生了什么?

將 D 對象轉換為 C 對象

這不是我要描述的方式。 演員(C)檢查object 的類型。 如果通過,則進行分配。 如果不是,則拋出錯誤。

沒有更改或其他從一個“到”另一個完成。 強制轉換僅測試 object 是否屬於請求的類型。 一旦發現 object 屬於請求的類型,則允許與該類型相關的操作(方法)。

一個推論操作是instanceOf關鍵字,它允許程序員在不拋出錯誤的情況下測試類型。

if( iy instanceOf C ) {
   // do C stuff
}
else
   System.err.println( "I couldn't do it." );

如果 c1 是一個 C 對象並且我們執行 c1.doOther(),那么 C 對象 c1 不是調用它自己的實例方法 doOther() 嗎? 如果是這種情況,那么 this.getClass().getSimpleName() 應該返回 DoOther C

類型系統基本上分為兩半。 有你向編譯器聲明的類型(這里是IY ,然后你轉換為C ),然后是 object 的實際類型。 允許的操作(即方法調用)基於聲明的類型。

但是,實際調用是基於 object 本身的類型,在這種情況下,無論聲明的類型如何,它始終是D 所以你總是得到D對作為IYC類型的解釋。 這里總是調用D 同樣,沒有基於強制轉換或分配的更改。 D的類型是“多態的”並且可以“看起來像” IYC ,但它始終是真正的D

TL;DR:強制轉換不會改變底層實例的類型,它只是將它作為不同類型的變量公開給所有以后的代碼。


比喻:

你有一個盒子,你在里面放了一個名叫 Alice 的會計師。 你把 label 放在那個寫着“會計師”的盒子上。

然后,您將另一個 label 放在上面,上面寫着“高級會計師”。 這並沒有改變盒子里的愛麗絲。 Alice 的會計工作與任何其他高級會計師沒有任何不同,因此他們不會忽視這種行為。

當您要求 Alice 進行doAccounting時,由於他們沒有覆蓋該行為,因此將運行doAccounting的基本高級會計師工作流程,但作為該工作流程的一部分,有一個步驟可以獲取會計師姓名以簽署文檔。

您會驚訝於文件上的結果簽名是“愛麗絲”嗎?


更多詳情:

IY iy = new D(); // (1)
C c1 = (C) iy; // (2)
c1.doOther();  // (3)

(1) 創建一個D類型的實例,並將其存儲為變量iy 您將此變量指定為IY類型,這意味着使用該變量的任何東西都只會看到它是IY的一個實例。 這並不意味着它不再D ,只是你沒有將它暴露給任何以后的代碼。 但是該變量只是指向新創建的D實例的“指針”。

(2) 您在步驟 (1) 中創建的D實例轉換為類型C ,並將其存儲在新變量c1中。 這是在運行時評估的,並且它成功,因為正在轉換的iy變量是D類型(如上所述)並且通過您的 class 定義D extends C - 即所有D都是C (但不是反之亦然)。 您將c1變量聲明為具有類型C ,這很好 - 但這不會改變底層實例仍然是您在上面創建的D的事實。 同樣, c1現在是指向前一個D實例的“指針”。 您剛剛接觸到至少屬於C類型的后續代碼。

(3) 您在c1變量上調用實例方法doOther 這是在c1指向的基礎實例上調用的。 如上所述,這仍然是您在步驟 (1) 中創建的類型D的同一實例。 所以它着眼於實例doOther方法。 然后,這會查看類型D是否具有要調用的doOther的覆蓋,但由於它不會轉到層次結構中來自類型C的下一個。 作為該方法的一部分,使用了getClass() 很像doOther調用,這是在您的D實例上調用的,並且確實有自己的getClass()方法(因為所有新類都會)。 因此調用類型DgetClass() ,您會看到所看到的結果。

IY iy = new D();
C c1 = (C) iy;
c1.doOther();

As per the code you shared IY, C, A are parent of D. Whatever type you changing your D object, it is still D object only the reference are going to change, not the D object. 當您調用 c1.doOther() 時,它正在使用 D object 調用,您將獲得 DoOther D。

該類型僅限制您可以調用的任何方法。 如果引用是 IY,那么您只能調用 doOther。 如果它指向 C,那么您可以訪問 C 擁有的所有內容。 它將像這樣 go...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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