簡體   English   中英

Java轉換之謎-Class.cast vs Cast運算符

[英]Java casting mystery - Class.cast vs cast operator

基於此oracle doc示例,我嘗試創建自己的示例進行測試。 當然,建議的參考示例工作。 但是當我嘗試基於Class.cast方法時,我遇到了一個問題:

我的代碼是

CL3延伸CL2延伸CL1延伸CL0延伸Base。

    public class CL3 extends CL2 {
        int x = 3;
        public int getX() { return x; }
    }

    public class CL2 extends CL1 {
        int x = 2;
        public int getX() { return x; }
    }

    public class CL1 extends CL0 {
        int x = 1;
        public int getX() { return x; }
    }

    public class CL0 extends Base {
        int x = 0;
        public int getX() { return x; }

        public String getPath() {
            System.out.println("before obj    : " + getClass().cast(this));
            System.out.println("before class  : " + getClass());
            System.out.println("before x      : " + getClass().cast(this).x);
            System.out.println("before getX() : " + getClass().cast(this).getX());
            System.out.println();
            return getClazzPath(getClass());
        }
    }

    public abstract class Base {
        int x = -1;
        abstract int getX();

        @SuppressWarnings("unchecked")
        public <T extends CL0> String getClazzPath(Class<T> clazz) {
            System.out.println("clazz       : " + clazz);
            System.out.println("cast        : " + clazz.cast(this));
            System.out.println("cast.x      : " + clazz.cast(this).x);
            System.out.println("cast.getX() : " + clazz.cast(this).getX());
            System.out.println("#");
            return clazz.cast(this).x + (clazz == CL0.class ? "" : "/" + getClazzPath((Class<T>) clazz.getSuperclass()));
        }
    }

主要功能代碼為:

    public static void main(String[] args) {
        CL3 cl3 = new CL3();

        System.out.println("cl3.getX()=" + cl3.getX());
        System.out.println("((CL2)cl3).getX()=" + ((CL2) cl3).getX());
        System.out.println("((CL1)cl3).getX()=" + ((CL1) cl3).getX());
        System.out.println("((CL0)cl3).getX()=" + ((CL0) cl3).getX());
        System.out.println("((IdentyfiedScope)cl3).getX()=" + ((Base) cl3).getX());

        System.out.println();
        System.out.println("cl3.x=" + cl3.x);
        System.out.println("((CL2)cl3).x=" + ((CL2) cl3).x);
        System.out.println("((CL1)cl3).x=" + ((CL1) cl3).x);
        System.out.println("((CL0)cl3).x=" + ((CL0) cl3).x);
        System.out.println("((IdentyfiedScope)cl3).x=" + ((Base) cl3).x);

        System.out.println();
        System.out.println(cl3.getPath());
    }

輸出為:

cl3.getX()=3
((CL2)cl3).getX()=3
((CL1)cl3).getX()=3
((CL0)cl3).getX()=3
((IdentyfiedScope)cl3).getX()=3

cl3.x=3
((CL2)cl3).x=2
((CL1)cl3).x=1
((CL0)cl3).x=0
((IdentyfiedScope)cl3).x=-1

before obj    : test.code.hierarchy.read.CL3@70dea4e
before class  : class test.code.hierarchy.read.CL3
before x      : 0
before getX() : 3

clazz       : class test.code.hierarchy.read.CL3
cast        : test.code.hierarchy.read.CL3@70dea4e
cast.x      : 0
cast.getX() : 3
#
clazz       : class test.code.hierarchy.read.CL2
cast        : test.code.hierarchy.read.CL3@70dea4e
cast.x      : 0
cast.getX() : 3
#
clazz       : class test.code.hierarchy.read.CL1
cast        : test.code.hierarchy.read.CL3@70dea4e
cast.x      : 0
cast.getX() : 3
#
clazz       : class test.code.hierarchy.read.CL0
cast        : test.code.hierarchy.read.CL3@70dea4e
cast.x      : 0
cast.getX() : 3
#
0/0/0/0

問題是- 為什么當我們使用Class.cast(由getPath()。getClazzPath()方法調用)時,當直接訪問x字段時,結果與強制轉換運算符產生的結果不同? 如我們所見,getClazzPath方法('clazz:')的輸出返回正確的類型CL3-> CL2-> CL1-> CL0,但x始終引用為0。

我發現這與getClazzPath方法中的T類型有關-但我不知道如何正確解釋。

如果這里有任何專家可以解釋為什么在我的情況下強制轉換運算符和Class.cast之間的行為不同?

類型轉換不會更改對象的類型。 它僅更改對其引用的編譯時類型,並在需要時進行有關有效性的運行時檢查。

由於強制轉換不會更改對象的類型,因此它永遠不會更改可重寫方法(如getX()的調用結果,該方法將始終調用最特定的方法。 同樣,將對象附加到String ,強制轉換無效,因為結果將是可重寫方法toString()的調用。

對於字段或private方法,引用的編譯時類型可能會影響訪問哪個字段或方法,但是,由於有關類型的知識僅限於通用方法的類型參數,因此您可以不要期望僅呼叫者知道的類型受到影響。

所以當你更換

@SuppressWarnings("unchecked")
public <T extends CL0> String getClazzPath(Class<T> clazz) {
    System.out.println("clazz       : " + clazz);
    System.out.println("cast        : " + clazz.cast(this));
    System.out.println("cast.x      : " + clazz.cast(this).x);
    System.out.println("cast.getX() : " + clazz.cast(this).getX());
    System.out.println("#");
    return clazz.cast(this).x + (clazz == CL0.class ? "" : "/" + getClazzPath((Class<T>) clazz.getSuperclass()));
}

@SuppressWarnings("unchecked")
public <T extends CL0> String getClazzPath(Class<T> clazz) {
    System.out.println("clazz       : " + clazz);
    System.out.println("cast        : " + (T)this);
    System.out.println("cast.x      : " + ((T)this).x);
    System.out.println("cast.getX() : " + ((T)this).getX());
    System.out.println("#");
    return clazz.cast(this).x + (clazz == CL0.class ? "" : "/" + getClazzPath((Class<T>) clazz.getSuperclass()));
}

結果沒有改變。 有一個普通的類型轉換之間沒有差異(T)…clazz.cast(…)clazz是一個Class<T>兩者都具有改變參考到的編譯時間類型的效果T ¹。

但是什么是T 該方法不知道,由於聲明<T extends CL0> CL0.x <T extends CL0> ,該方法可分配給CL0 ,因此該方法允許訪問CL0成員,包括字段CL0.x

您可能會認為這是語言設計錯誤,它允許通過類型T的引用訪問不可覆蓋的成員(如CL0字段),盡管T可能是一個子類,聲明了自己的同名字段。 實際上,對於private成員,即使可以訪問CL0 private成員,當通過類型T的引用訪問它們時,編譯器也會生成錯誤。


為了進一步證明普通類型的演員表和Clazz.cast之間沒有區別,您可以更改

System.out.println("cl3.getX()=" + cl3.getX());
System.out.println("((CL2)cl3).getX()=" + ((CL2) cl3).getX());
System.out.println("((CL1)cl3).getX()=" + ((CL1) cl3).getX());
System.out.println("((CL0)cl3).getX()=" + ((CL0) cl3).getX());
System.out.println("((IdentyfiedScope)cl3).getX()=" + ((Base) cl3).getX());

System.out.println();
System.out.println("cl3.x=" + cl3.x);
System.out.println("((CL2)cl3).x=" + ((CL2) cl3).x);
System.out.println("((CL1)cl3).x=" + ((CL1) cl3).x);
System.out.println("((CL0)cl3).x=" + ((CL0) cl3).x);
System.out.println("((IdentyfiedScope)cl3).x=" + ((Base) cl3).x);

您的main方法

System.out.println("cl3.getX()=" + cl3.getX());
System.out.println("((CL2)cl3).getX()=" + CL2.class.cast(cl3).getX());
System.out.println("((CL1)cl3).getX()=" + CL1.class.cast(cl3).getX());
System.out.println("((CL0)cl3).getX()=" + CL0.class.cast(cl3).getX());
System.out.println("((IdentyfiedScope)cl3).getX()=" + Base.class.cast(cl3).getX());

System.out.println();
System.out.println("cl3.x=" + cl3.x);
System.out.println("((CL2)cl3).x=" + CL2.class.cast(cl3).x);
System.out.println("((CL1)cl3).x=" + CL1.class.cast(cl3).x);
System.out.println("((CL0)cl3).x=" + CL0.class.cast(cl3).x);
System.out.println("((IdentyfiedScope)cl3).x=" + Base.class.cast(cl3).x);

結果也一樣。 這兩種方法之間沒有什么區別,重要的是,在一處,您將轉換為具體類型CL3CL2CL1CL0Base ,而另一處,則轉換為類型參數T


¹與未檢查的Class.cast不同, Class.cast將在運行時檢查有效性,不同之處在於,由於它們在此示例中都有效,因此結果不會改變,我們可以集中討論對成員選擇的影響。

謝謝霍爾格的解釋。 縮短歷史記錄。 在我的示例中,我不能基於字段不是多態的事實,因為字段引用是在編譯時解析的

在主要方法中,代碼為:

System.out.println("cl3.x=" + cl3.x);
System.out.println("((CL2)cl3).x=" + ((CL2) cl3).x);
System.out.println("((CL1)cl3).x=" + ((CL1) cl3).x);
System.out.println("((CL0)cl3).x=" + ((CL0) cl3).x);
System.out.println("((IdentyfiedScope)cl3).x=" + ((Base) cl3).x);

產生輸出:

cl3.x=3
((CL2)cl3).x=2
((CL1)cl3).x=1
((CL0)cl3).x=0
((IdentyfiedScope)cl3).x=-1

因為在編譯時例如'(((CL1)cl3).x)'更改為'CL1.x'。

方法的控制台輸出:

public <T extends CL0> String getClazzPath(Class<T> clazz) {
    System.out.println("clazz       : " + clazz);
    System.out.println("cast.x      : " + clazz.cast(this).x);
}

將始終為'cast.x:0'打印,因為在編譯時,編譯器始終將其解析為'CL0.x'。 在那個地方,無論在運行時報告什么混亂,因為在編譯時將始終使用CL0.x。 這是合乎邏輯的,因為我們不能確保CL0的子級具有x字段。

clazz       : class test.code.hierarchy.read.CL3
cast.x      : 0

clazz       : class test.code.hierarchy.read.CL2
cast.x      : 0

暫無
暫無

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

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