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