Java方法和變量的繼承問題

想必你已經閱讀了一兩本這樣的Java書籍,它們在開頭都指出了面向對象編程的3個主要概念:封裝、繼承和多態。理解這3個概念對於領會Java 語言來說至關重要,而搞懂方法的覆蓋又是理解繼承概念的關鍵部分。
這個例子摘自 Java 語言規範

01:  class Super
02:  {
03:      static String greeting()
04:      {
05:         return "Goodnight";
06:      }
07:
08:      String name()
09:      {
10:         return "Richard";
11:      }
12:  }
 
01:  class Sub extends Super
02:  {
03:      static String greeting()
04:      {
05:         return "Hello";
06:      }
07:
08:      String name()
09:      {
10:         return "Dick";
11:      }
12:  }
 
01:  class Test
02:  {
03:      public static void main(String[] args)
04:      {
05:         Super s = new Sub();
06:         System.out.println(s.greeting() + ", " + s.name());
07:      }
08:  }
運行 Test 類的結果如下
Goodnight, Dick
要是你得出了同樣的輸出結果,那麼你或許對方法的覆蓋有了較好的理解,如果你的結果和答案不一致,那就讓我們一起找出原因,我們先分析一下各個類:Super類由方法 greeting和name組成,Sub 類繼承了 Super 類,而且同樣含有 greeting 和 name方法。Test 類只有一個 main方法。在 Test 類的第5 行中,我們創建了一個 Sub 類的實例。在這裏,你必須明白的是:雖然變量 s的數據類型爲 Super 類,但是它仍舊是 Sub 類的一個實例,如果你對此有些迷惑,那麼可以這樣理解: 變量s 是一個被強制轉換爲 Super 型的Sub 類的實例。
下一行(第 6 行)顯示了s.greeting()返回的值,加上一個字符串,緊隨其後的是 s.name()的返回值。關鍵問題就在這裏,我們調用的到底是Super類的方法還是Sub類的方法,讓我們首先判斷調用的是哪個類的name()方法,兩個類中的name()方法都不是靜態方法,而是實例方法,因爲Sub類繼承了Super類,而且有一個和它父類同樣標識的name()方法,所以Sub類中的name()
方法覆蓋了Super類中的name()方法,那麼前面提到的變量s又是Sub 類的一個實例,這樣一來 s.name()的返回值就是“Dick”了。
至此,我們解決了問題的一半,現在我們需要判斷被調用的greeting()方法究竟是Super類的還是Sub類的。需要注意的是,兩個類中的greeting()方法都是靜態方法,也稱爲類方法。儘管事實上Sub類的greeting()方法具有相同的返回類型、相同的方法名以及相同的方法參數。然而它並不覆蓋Super類的greeting()方法,由於變量s被強制轉換爲Super型並且Sub類的greeting()方法沒有覆蓋Super類的greeting()方法,因此 s.greeting()的返回值爲Goodnight。
還是很迷惑?請記住這條規則:“實例方法被覆蓋,靜態方法被隱藏”。
現在你可能會問“隱藏和覆蓋有什麼區別”你也許還未理解這點。然而實際上我們剛剛在這個Super/Sub 類的例子中已經解釋了兩者的不同。使用類的全局名可以訪問被隱藏的方法,即使變量s是Sub類的一個實例,而且Sub類的greeting()方法隱藏了Super 類的同名方法,我們仍舊能夠將s強制轉換爲Super型以便訪問被隱藏的greeting()方法,與被隱藏的方法不同,對被覆蓋的方法而言,除了覆蓋它們的類之外,其他任何類都無法訪問它們。這就是爲何變量s調用的是Sub類的name(),而非Super類的name()方法。
也許對你來說 理解隱藏靜態方法和覆蓋實例方法的區別的最佳方式,就是自己創建幾個類似於Sub/Super的類,再重複一次規則,實例方法被覆蓋而靜態方法被隱藏,被覆蓋的方法只有覆蓋它們的類才能訪問它們,而訪問被隱藏的方法的途徑是提供該方法的全局名。現在你終於明白標題裏問題的答案了吧。什麼時候“被覆蓋的”方法並非真地被覆蓋了呢?答案就是“永遠不會”。另外,還有幾個要點,請謹記:
--試圖用子類的靜態方法隱藏父類中同樣標識的實例方法是不合法的,編譯器將會報錯
--試圖用子類的實例方法覆蓋父類中同樣標識的靜態方法也是不合法的,編譯器會報錯
--靜態方法和最終方法(帶關鍵字final的方法)不能被覆蓋
--實例方法能夠被覆蓋
--抽象方法必須在具體類中被覆蓋
現在我們來看繼承時變量覆蓋和隱藏的問題,如果你認爲你已經理解了上面的方法繼承時的覆蓋和隱藏問題,繼而認爲變量也如此的話,那麼請繼續往下看:
Java共有6種變量類型:類變量、實例變量、方法參數、構造函數參數、異常處理參數和局部變量。類變量包括在類中定義的靜態數據成員以及在接口中聲明的靜態或非靜態的數據成員。實例變量是在類體中聲明的非靜態變量,術語“變量成員”指的是類變量和實例變量。方法參數是用來傳入方法體的。構造函數參數是用來傳入構造函數的。異常處理參數用來傳入一個try語句中的catch塊的。最後,局部變量是在一個代碼塊或一個for語句中聲明的變量。

class Base {
 int x = 1;
 static int y=2;
 int z=3;
 int method() {
  return x;
 }
}
class Subclass extends Base {
 int x = 4;
 int y=5;
 static int z=6;
 int method() {
  return x;
 }
}
public class Test {
 public static void main(String[] args) {
  Subclass s=new Subclass();
  System.out.println(s.x + " " + s.y +" "+ s.z);
  System.out.println(s.method());
 
  Base b = (Subclass)s;
  System.out.println(b.x + " " + b.y +" "+ b.z);
  System.out.println(b.method());
 }
}
運行可以得到輸出:
4 5 6
4
1 2 3
4
由此我們可以得出:
實例變量和類變量能被隱藏,被子類的同名變量成員隱藏。局部變量和各種參數永遠不會被隱藏(參見下例)。

class Hidden
  {
      public static void main(String args[])
      {
     int args=0;      //compile error
    String s="abc";
  int s=10;        //compile error
      }
  }
如何訪問被隱藏的變量呢? 使用“this”關鍵字可以訪問被局部變量隱藏的本類中的實例變量,關鍵字“super”可以訪問父類中被隱藏的實例變量,類變量可以用類加“.”來訪問。強制轉換爲父類型。
變量和方法覆蓋和隱藏的不同:一個類的實例無法通過使用全局名或者強制自己轉換爲父類型,以訪問父類中被隱藏的方法,然而強制轉換子類爲父類型之後,可以訪問父類中被隱藏的變量。另外靜態方法不能覆蓋父類的實例方法,而靜態變量卻可以隱藏父類的一個同名實例變量,同樣,實例方法不能覆蓋父類的同名靜態方法,而變量卻可以隱藏父類的同名變量成員,不論父類的這個變量成員是類變量或者是實例變量。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章