[短文速讀] 重載有暗坑,重載重寫你真的瞭解麼

前言

這將是一個系列文章。原因是自己寫了很多文章,也看了很多文章。從最開始的僅僅充當學習筆記,到現在認認真真去寫文章去分享。中間發現了很多事情,其中最大發現是:收藏不看!總是想着先收藏以後有時間再看,到後來…大家都懂得。大多數文章彷彿石沉大海,失去了應有的價值。

因爲技術文章大多需要比較重的思考,但是現如今時間碎片化很嚴重,因此收藏不看也實屬不得已。所以萌生了這個系列的想法,系列文章的特點:以一些日常開發中不起眼的基礎知識點爲內核,圍繞此包裹通俗易懂的文字。儘量用少思考的模式去講述一個知識。讓我們能夠真正在碎片化的時間裏學到東西!

出場角色

小A:剛踏入Java編程之路…

MDove:一個快吃不上飯的Android開發…

正題

引子

小A:MDove,我最近遇到一個問題百思不得其解。

MDove:正常,畢竟你這智商1+1都不知道爲什麼等於2。

小A:那1+1爲啥等於2呢?

MDove:......說你遇到的問題。


重載不理解

小A:是這樣的,我在學習多態的時候,重載和重寫,有點蒙圈了...

public class MethodMain {
    public static void main(String[] args) {
        MethodMain main = new MethodMain();
        Language language = new MethodMain().new Java();
        Language java = new MethodMain().new Java();

        main.sayHi(language);
        main.sayHi(java);
    }

    private void sayHi(Java java) {
        System.out.println("Hi Java");
    }

    private void sayHi(Language language) {
        System.out.println("Im Language");
    }

    public class Java extends Language {}
    public abstract class Language {}
}

[短文速讀] 重載有暗坑,重載重寫你真的瞭解麼

小A:程序運行結果爲什麼是這個呀?我覺得它應該一個是Im Language一個是Hi Java呀。

MDove:原來是這個疑惑呀。好,那今天就好好聊一聊重載/重寫背後:方法調用的原理。爲了更好理解,我儘量不用學術性強的語言來解釋。開始之前讓我們先看一行代碼:

如果想了解更專業的內容,可以參考《Java虛擬機規範》或者《深入理解Java虛擬機》。

A a = new B();

MDove:對於A和B來說,他們有不同的學術名詞。A稱之爲靜態類型,B稱之爲實際類型。對於Language language = new MethodMain().new Java();也是如此:Language是靜態類型,Java是實際類型

MDove:從你寫的demo裏,我們可以看出來:main.sayHi(language); main.sayHi(java);最終都是調用了private void sayHi(Language language)。我們是不是可以得出一個結論:方法的調用是根據靜態類型去匹配的?
就像你的那個demo一樣,language和java的靜態類型都是Language所以就匹配了private void sayHi(Language language)這個方法。


重寫不明白

小A:不對啊!!!如果用Override,重寫的話,這個結論是不成立的!

public class MethodMain {
    public static void main(String[] args) {
        Language language = new MethodMain().new Java();

        language.sayHi();
    }

    public class Java extends Language {
        @Override
        public void sayHi() {
            System.out.println("Hi,Im Java");
        }
    }

    public class Language {
        public void sayHi() {
            System.out.println("Hi,Im Language");
        }
    }
}

[短文速讀] 重載有暗坑,重載重寫你真的瞭解麼

MDove:別急,你這是面向對象多態神經紊亂綜合徵。說白了就是看串了。你難道不覺得,這倆個demo寫法上有不同麼?或者再上升一下重載和重寫是不是有不同之處?

小A:你這麼一說好像真是!重載是在一個類裏邊折騰;而重寫子類折騰父類

MDove:沒錯,正式如此。導致了JVM在加載方法的時候採用了不同的方式。因此也就有了你所感到疑惑的,爲什麼重載會是這種結果,而重寫會是那種結果。

小A:那可不可以最多講一講加載方法的不同之處的?

JVM如何調用方法

MDove:將調用之前,我們再回到上文提到的靜態類型上。對於JVM來說,在編譯期變量的靜態類型是確定的,同樣重載的方法也就能夠確定。很好理解,因爲二者都是確定無誤的。所以對於這種方法,JVM採用靜態分派的方式去調用。

MDove:說白了就是,在編譯期就決定好該怎麼調用這個方法。因此對於在運行期間生成的實際類型JVM是不關心的。只要你的靜態類型是郭德綱,就算你new一個吳亦凡出來。這行代碼也不能又長又寬...

小A:照這個邏輯來說,重寫就是動態分派,需要JVM在運行期間確定對象的實際類型,然後再決定調用哪個方法。

MDove:沒錯,畢竟重寫涉及到你是調用子類的方法還是調用父類。因此需要在運行期間去決定。當然我們用嘴說是很輕巧的,實際JVM去執行時是很複雜的過程。如果你感興趣可以去了解這方面的知識。

重載的暗坑

MDove:因爲重載的性質,重載在可變參數上是有坑的。我寫的demo,你瞅瞅能不能感覺出奇怪的地方:

public class MethodMain {
    public static void main(String[] args) {
        MethodMain main = new MethodMain();
        main.fun(null, 666);
        main.fun(null, 666, 666);
    }

    private void fun(Object obj, Object... args) {
        System.out.println("fun(Object obj, Object... args)");
    }

    private void fun(String string, Object obj, Object... args) {
        System.out.println("fun(String string, Object obj, Object... args)");
    }
}

小A:我覺得應該是打印:fun(Object obj, Object... args)和fun(String string, Object obj, Object... args)吧?

MDove:最開始我也是這麼認爲的。我們從我們的角度出發,很自然的認爲main.fun(null, 666);應該調用private void fun(Object obj, Object... args),而main.fun(null, 666, 666);去調用private void fun(String string, Object obj, Object... args)

MDove:可以如果我們站在程序的角度呢?因爲我們寫的是可變參數,程序怎麼可能知道666和666,666應該去對應哪個方法。所以這個demo的結果是:

[短文速讀] 重載有暗坑,重載重寫你真的瞭解麼

小A:那我有一個疑問,既然程序很難洞察應該調用哪個可變參數的方法,那它又是爲什麼調用了下邊的而不是上邊的呢?

MDove:那是因爲編譯期在匹配方法時,如果有多個可能性,它會使用更向下的類型,結合上述的demo。因爲我們傳入null時,雖然即能滿足Object又能滿足String。但由於String是 Object的子類(也就是更向下),因此編譯器會認爲第二個方法更爲貼切。

小A:Skr,Skr...

static重寫

MDove:我們繼續聊一聊重寫,咱們說了普通的重寫。靜態的重寫豈能不提。首先來說static不能稱之爲重寫,只能叫做隱藏父類實現。文字很抽象,直接看代碼:

public class MethodMain {
    public static void main(String[] args) {
        Language.sayHi();
        Java.sayHi();
    }
}

public class Java extends Language {
    public static void sayHi() {
        System.out.println("Hi,Im Java");
    }
}

public class Language {
    public static void sayHi() {
        System.out.println("Hi,Im Language");
    }
}

[短文速讀] 重載有暗坑,重載重寫你真的瞭解麼

MDove:說白了就是:老子是老子的,兒子是兒子的。其實這個也比較好理解。static的變量、方法都是伴隨類存在的,類加載完畢就生成了。它和對象new不new是沒有關係的。因此也不存在什麼實際變量一說。因此也就有了上邊的這種情況,也就是常說的:隱藏父類。

小A:這些內容,學習的時候還真沒有好好的去思考...以後要加油了!

劇終

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章