前言
關於所有Java系列文章面向有一定基礎的童鞋,所寫每一篇希望有一定含金量,有些內容可能會從Java整個語法全域性考慮穿插後續要講解的內容以成系統,若不理解,請看完後再學習。上一節我們講解完了final關鍵字,本節我們繼續來對比講解Java和C#中的重寫,二者語言的重寫區分非常清晰,Java子類中基類方法簽名一樣或通過註解@Override顯式宣告,C#中基類通過virtual關鍵字修飾,子類通過ovveride關鍵字表示重寫,具體細節請往下看。
重寫
既然是重寫必然就涉及到繼承,我們首先來看看Java中的重寫是怎樣的呢?如下:
public class Main { public void f() { System.out.println("Main.f"); } public static void main(String[] args) { Main main = new Sub(); main.f(); } } class Sub extends Main { public void f() { System.out.println("Sub.f"); } }
當呼叫基類的f方法時,此時發現已被子類所重寫,所以如上正常列印出Sub.f,要是我們將上述基類中方法修改為私有的呢
可能我們期待輸出子類中的列印結果,但是修改為私有後,說明此時對子類不再可見,也就相當於使用了final,在這種情況下,子類中方法則是一個全新的方法,很顯然說明:只有非私有方法才可以被重寫。對於這種情況下編譯不會報錯, 但是也不會按照我們所期望的結果來執行,所以建議對於基類中的私有方法命名為和子類不同的名字,為了讓重寫一目瞭然或更加明確,在1.5版本釋出了註解功能,我們可以通過註解來顯式宣告要重寫基類方法,若基類為私有,此時通過註解則會編譯報錯,因為找不到要重寫的方法,這種體驗更加友好,比如如下:
public class Main { private void f() { System.out.println("Main.f"); } public static void main(String[] args) { Main main = new Sub(); main.f(); } } class Sub extends Main { //編譯錯誤,未找到基類(超類)中要重寫的方法 @Override public void f() { System.out.println("Sub.f"); } }
舉一反三,我們來思考一個問題,是不是方法簽名一致,子類就可以重寫基類方法呢?很顯然不是,若是靜態方法,必然不能被重寫,如果通過註解@Override宣告那麼必然編譯報錯,否則呼叫基類方法,對於介面中的靜態方法同理。所以還是建議使用註解來宣告重寫。那麼為什麼通過註解顯式宣告重寫基類方法或通過關鍵字final修飾方法就會在編譯階段報錯呢?因為它們在編譯階段就完成了靜態繫結,而不是執行時動態繫結。問題又來了,上述我們講解到若在子類中不通過註解顯式宣告重寫,同時在基類中方法私有,此時一定可以編譯通過(上述已演示),並且會呼叫基類方法並列印出結果,事實情況一定是這樣嗎?很顯然也不是如此,如下示例:
class Super { private void f() { System.out.println("Super.f"); } } class Derived extends Super { public void f() { System.out.println("Derived.f"); } public static void main(String[] args) { Super aSuper = new Derived(); //編譯報錯(因為基類方法私有) aSuper.f(); } }
初一看,這不是一樣麼,其實是不一樣,上述可以那是因為呼叫方在基類裡面,當然可以呼叫內部的私有方法,如上情況只對基類內部私有, 當然也就不能呼叫,這裡就又引出一個問題,是不是宣告為基類的私有方法,子類就無法進行重寫呢?根據我們上述列印的結果來看,理論上不可行,事實情況是可以的,通過內部類(後續會出文章詳細講解)來實現。
class Main { private void f() { System.out.println("Main.f"); } class Inner extends Main { private void f() { System.out.println("Inner.f"); } } public static void main(String args[]) { //內部類例項必須通過外部類例項建立 Main outer = new Main(); Inner inner = outer.new Inner(); //內部類可以在內部訪問外部的所有成員(包括私有) inner.f(); // 呼叫外部類方法 outer = inner; outer.f(); } }
C#若明確需要重寫,那麼基類方法宣告為虛有的virtual,子類通過ovverride關鍵字修飾方法達到重寫目的,若沒有這兩個關鍵字,和Java中一樣只是方法簽名一致,那麼說明編譯器會提醒是否通過new關鍵字來表明隱藏基類的方法
class Program { public void F() { Console.WriteLine("Main.f"); } static void Main(string[] args) { Program program = new Sub(); program.F(); Console.ReadKey(); } } class Sub : Program { public new void F() { Console.WriteLine("Sub.f"); } }
總結
Java和C#中的重寫區分度非常清晰,Java中只要方法簽名一致就可以達到重寫,不過建議通過註解方法來顯式宣告重寫以免引起不必要的問題,同時,即使基類方法私有,我們也可藉助於內部類來實現重寫。