想必你已經閱讀了一兩本這樣的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”可以訪問父類中被隱藏的執行個體變數,類變數可以用類加“.”來訪問。強制轉換為父類型。
---變數和方法覆蓋和隱藏的不同:一個類的執行個體無法通過使用全域名或者強制自己轉換為父類型,以訪問父類中被隱藏的方法,然而強制轉換子類為父類型之後,可以訪問父類中被隱藏的變數。另外靜態方法不能覆蓋父類的執行個體方法,而靜態變數卻可以隱藏父類的一個同名執行個體變數,同樣,執行個體方法不能覆蓋父類的同名靜態方法,而變數卻可以隱藏父類的同名變數成員,不論父類的這個變數成員是類變數或者是執行個體變數。