2011-05-15 45 views
15

我正在接受一門Java考試的培訓,並且我遇到了一些我在去年的主題中不瞭解的內容。這裏是代碼Java「詭計」,重新定義了女兒班的成員

class Mother { 
    int var = 2; 

    int getVar() { 
     return var; 
    } 
} 

class Daughter extends Mother { 
    int var = 1; 

    int getVar() { 
     return var; 
    } 

    public static void main(String[] args) { 
     Mother m = new Mother(); 
     System.out.println(m.var); 
     System.out.println(m.getVar()); 
     m = new Daughter(); 
     System.out.println(m.var); 
     System.out.println(m.getVar()); 
    } 
} 

問題是「這個程序的輸出是什麼?」。我會去與2 2 1 1,但編譯和運行這段代碼時,我得到2 2 2 1.

任何人都可以解釋我爲什麼?

感謝您的閱讀!

+2

'女兒'延伸'母親'?這很奇怪,因爲實際上它是相反的。 – mbx 2011-05-15 12:57:49

+0

我也有興趣聽到原因。我在Eclipse中運行它,並用調試器檢查了值,調試器實際上在m = new Daugher()-line後面顯示了具有兩個不同var成員的本地m變量。 m.var似乎解析爲母親中的一個(也許是因爲局部變量被聲明爲Mother,不知道?),並且m.getVar()調用Daughter中的getVar(如預期的那樣)。 – esaj 2011-05-15 12:58:21

+0

請注意,在真正的程序中,這不會發生,因爲通常你會使'var'變成私人的。如果你想讓課堂以外的課程可以訪問,這應該是非常罕見的,那麼你應該確保變量不會互相隱藏。 – starblue 2011-05-15 14:26:52

回答

16

方法調用m.getVar()是一個虛擬方法調用。第二次調用它時,它會動態地派發到派生的Daughter.getVar(),它按照您的期望執行(訪問Daugther.var並返回)。

成員字段沒有這種虛擬調度機制。因此m.var總是指Mother.var,即該變量的基類的版本。

Daughter類可以看作有兩個不同的var成員:Mother和它自己的成員。它自己的成員「隱藏」Mother中的一個,但可以通過使用super.varDaughter類訪問。

官方對此的說明位於JLS8.3 Field Declarations部分。 報價:

如果類聲明的字段具有特定名稱,那麼該字段的聲明,以隱藏任何與在超同名字段的所有訪問聲明和的超級說類。字段聲明還隱藏了封閉類或接口中任何可訪問字段的聲明(第6.3.1節),以及在任何封閉塊中具有相同名稱的任何局部變量,形式方法參數和異常處理程序參數。

注意,它可以變得非常有趣(強調):

如果字段聲明隱藏另一個字段的聲明中,兩個字段不必有相同的類型

和:

有可能是由同一個字段聲明可能會從接口繼承幾條路徑。在這種情況下,該字段被認爲只能被繼承一次,並且可以通過簡單的名稱來引用該字段而沒有歧義。

所以那款很值得一讀:-)

+0

所以如果我正確的做到了,當派生類重新定義成員字段時,直接訪問這個字段將返回父類字段,而通過方法訪問它將返回正確的字段。是嗎? – ksol 2011-05-15 13:02:12

+0

不完全。取決於你如何訪問它。如果您的對象被稱爲女兒,那麼您將訪問派生類字段 – Heisenbug 2011-05-15 13:04:22

+0

取決於您正在使用的引用的類型。如果你曾經使用過'女兒d = new Daugther(); System.out.println(d.var);'顯然會返回'1'。 – Mat 2011-05-15 13:04:24

6

集中在這些線路:

Mother m; 
m = new Daughter(); 
System.out.println(m.var); 
System.out.println(m.getVar()); 

您正在構建一個女兒的對象,但喜歡它的基類的媽媽你對待它。所以當你訪問米。var你正在訪問基類變量var。同時,當您調用方法時,即使您引用基類引用,也會調用被覆蓋的方法。 這是方法和領域的不同行爲。字段引用不能被覆蓋。

1
m = new Daughter(); 

雖然你已經創建了一個daughter對象,你指的是物體Mother m參考。因此,使用m任何調用會調用媽媽級成員,沒有女兒的

0

我在Eclipse中跑了這一點,並檢查值與調試器,調試器實際顯示當地m -variable具有m = new Daugher()後兩個不同的變種 - 成員 - 與值2和1一致。m.var似乎解析爲母親中的一個,並且m.getVar()調用Daughter中的getVar(如預期的那樣)。

但是,當我改變主方法是這樣的:

Mother m = new Mother(); 
    System.out.println(m.var); 
    System.out.println(m.getVar()); 
    Daughter d = new Daughter(); 
    System.out.println(d.var); 
    System.out.println(d.getVar()); 

它實際上輸出2,2,1,1,所以它似乎是變量的聲明影響哪一類的var用來。

2

可以覆蓋方法,但只能隱藏字段。區別在於非靜態方法使用引用的對象的類型,字段採用引用的類型。您會看到類似的靜態方法,只有在「引用」類和對象(如果提供)的類被忽略的情況下才會隱藏。

爲了您的興趣,請儘量給予不同類型的字段。 ;)

您也可以嘗試

System.out.println(((Mother)m).var); // uses var in Mother 
System.out.println(((Daughter)m).var); // uses var in Daughter 
0

我讀了答案,他們的非(迄今爲止)給了很好的理由在面向對象的語言,Java是,這應該是這樣的。我會盡力解釋。

假設你有函數,有媽媽爲ARG:

void foo(Mother m) { 
    print(m.var); 
} 

此功能(實際上是編譯器)不知道你是否會與Mother調用它,Daughter或與其他Dauther2那並不是」 t甚至有var變量聲明爲。因此,當引用的類型爲Mother時,必須將引用成員變量(由編譯器)鏈接到Mother的成員。 類似的適用於功能太,這樣的功能都與母親聲明getVar(),但不Mother實施getVar()

所以,成員變量總是連接(通過編譯器)基於參考。另一種方式來解釋:如果刪除Mother的VAR(並母親getVar()編譯),你第二m.var(當m指女兒)不會編譯甚至Daughter有成員變種。

我希望我很清楚。