Java入門系列之重寫

Jeffcky發表於2020-07-11

前言

關於所有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中只要方法簽名一致就可以達到重寫,不過建議通過註解方法來顯式宣告重寫以免引起不必要的問題,同時,即使基類方法私有,我們也可藉助於內部類來實現重寫。

相關文章