2011-04-12 68 views
14

考慮int a變量,這些類會發生什麼:當基類和派生類都具有相同名稱的變量

class Foo { 
    public int a = 3; 
    public void addFive() { a += 5; System.out.print("f "); } 
} 
class Bar extends Foo { 
    public int a = 8; 
    public void addFive() { this.a += 5; System.out.print("b "); } 
} 
public class test { 
    public static void main(String [] args){ 
     Foo f = new Bar(); 
     f.addFive(); 
     System.out.println(f.a); 
    } 
} 

我明白addFive()已經在子類中被覆蓋的方法,在課堂測試當引用子類的基類引用用於調用重寫的方法時,將調用子類版本addFive

但是公共實例變量a呢?當基類和派生類都具有相同的變量時會發生什麼?

上述程序的輸出是

b 3 

這是如何發生的呢?

+0

我對代碼做了一些更改,無法理解發生了什麼。我在Bar中添加了一個顯示方法,並在main中調用它,但是得到了編譯器錯誤。在Foo中添加了一個顯示方法,但令人驚訝地顯示了酒吧的顯示。這是Java的缺陷嗎? – 2014-08-26 03:48:53

+0

@SrujanBarai這就是多態性。 編譯時間:早期綁定:檢查發生在引用類型上。 運行時間:後期綁定:調用實際對象的類型。 – 2017-11-06 08:00:44

回答

18

實際上有兩個不同的公共實例變量,稱爲a

  • 一個Foo對象有一個Foo.a變量。
  • 一個條形對象同時具有Foo.aBar.a變量。

當你運行這個:

Foo f = new Bar(); 
    f.addFive(); 
    System.out.println(f.a); 

addFive方法更新Bar.a變量,然後讀取Foo.a變量。要閱讀Bar.a變量,你需要做的是:

System.out.println(((Bar) f).a); 

對這裏發生的一切的技術術語爲「隱藏」。例如,請參閱JLS section 8.3section 8.3.3.2

請注意,隱藏也適用於具有相同簽名的static方法。

但是,具有相同簽名的實例方法將被「重寫」而不是「隱藏」,並且您無法訪問從外部覆蓋的方法版本。 (在覆蓋某個方法的類中,重寫的方法可以使用super來調用,但這是唯一允許這種情況的原因,訪問重寫方法通常被禁止的原因是它會中斷數據抽象。)


推薦的方式避免混淆(意外)隱藏是聲明您的實例變量爲private並通過getter和setter方法訪問它們。有很多其他太多使用getters和setters的好理由。

+0

+1這是正確的,但我仍然不明白爲什麼 – 2011-04-12 04:54:33

+0

謝謝。有沒有辦法只使用f來調用基類的addFive? – moonsun 2011-04-12 04:54:41

+0

哇!我很震驚。我不得不運行這個來相信它。 Plz告訴我如何從外部訪問Bar的變量a? – euphoria83 2011-04-12 05:02:01

4

JLS

8.3.3.2實例:實例變量的隱藏該實施例類似於 ,在前面的部分中,但使用 實例變量而不是靜態 變量。代碼:

class Point { 
    int x = 2; 
} 
class Test extends Point { 
    double x = 4.7; 
    void printBoth() { 
     System.out.println(x + " " + super.x); 
    } 
    public static void main(String[] args) { 
     Test sample = new Test(); 
     sample.printBoth(); 
     System.out.println(sample.x + " " + 
               ((Point)sample).x); 
    } 
} 

產生輸出:

4.7 2 
4.7 2 

因爲 類X的聲明測試隱藏X的定義 級點,所以類測試不 繼承域x從它的 超類Point。但是,必須指出, 然而,儘管 類的字段x沒有被類 測試繼承,但它仍然由類Test的實例實現 。在其他 單詞中,Test 類的每個實例都包含兩個字段,其中一個類型爲int ,另一個類型爲double。兩個字段 都帶有名稱x,但在類Test的 聲明中,簡單的 名稱x始終指的是在Test類中聲明的字段 。代碼 實例方法類Test可能 引用實例變量x的 class Point as super.x。使用一個字段訪問 表達訪問域x

代碼將 訪問在通過引用 表達式的類型指示的類 名爲x領域。因此,表達 sample.x訪問雙精度值,在類 測試聲明的 實例變量,因爲變量 樣品的類型是Test,但表達 ((點)樣品).X訪問一個int 值,該實例變量在類Point中聲明爲 ,因爲該類型爲 。

2

在繼承中,Base類對象可以引用Derived類的實例。

所以這是如何Foo f = new Bar();工作好吧。

現在,當調用f.addFive();語句時,它實際上使用Base類的引用變量調用Derived類實例的addFive()方法。所以最終調用'Bar'類的方法。但是當你看到'Bar'類的addFive()方法只是打印'b'而不是'a'的值。

下一條語句,即System.out.println(f.a)是實際打印最終被附加到前一個輸出的a的值,因此您將最終輸出看作'b 3'。這裏使用的值是'Foo'類的值。

希望這個技巧執行&編碼很清楚,你知道你是如何得到輸出爲'b 3'的。

+1

+1哇,這是令人困惑的。很高興我切換到Python:^) – 2011-04-12 05:01:31

+1

@jcomeau_ictx - 但我看到你仍然喜歡在「java」標籤空間中巨魔...... :-) – 2011-04-12 10:38:06

+0

@Stephen C,我是否「巨魔」?我從來沒有故意惡意,我偶爾也會幫忙。我仍然需要使用Java,並且仍然知道一些有關它的事情。 – 2011-04-12 14:55:31

-1

這裏F是Foo類型,f變量是持有Bar對象,但是java運行時從類Foo中獲取f.a。這是因爲在Java中,變量名稱是使用引用類型解析的,而不是它引用的對象。

+0

此解釋不正確。實際上,它實際上是'f'的編譯時類型,它決定了'f.a'引用哪個'a'變量。 – 2011-04-12 05:21:18

+0

這裏的編譯時和運行時類型不一樣嗎,Foo? – euphoria83 2011-04-12 06:02:50

+0

不可以。編譯時間類型'f'是'Foo',但'f'引用的實際對象的運行時類型是'Bar'。這個解釋似乎是說這個決定是在運行時做出的,這是誤導性的......如果實際上不正確的話。 – 2011-04-12 10:45:28

相關問題