1. 程式人生 > >面向對象基本特征:多態

面向對象基本特征:多態

字節 erro 清晰 不同的 引用 向上轉型 virtual lang abstract

多態是面向對象最重要的特征。具體到Java中是如何體現的呢。

多態在我們的使用中其實就是重載與重寫。下面分別進行講述一下。

重載

重載的定義:一個類中,如果有兩個方法的方法名相同,但參數列表不同,可以說一個方法是另一個方法的重載。

註意2點:1.方法名相同 2.參數列表不同(參數列表為:參數的類型,參數的個數)

調用重載方法的時候,JVM會根據不同的參數列表來選擇合適的方法。

我們來看一下代碼:

public class Main {
    public static void main(String[] args) {
        Father f= new Father();
        f.print();
        f.print(
5); } } class Father{public void print() { System.out.println("father"); }public void print(int x) { System.out.println(x); } }

看一下字節碼:

技術分享圖片

可以很明顯看到invokevirtual調用了不同的方法。

重寫

當子類擁有和父類相同的方法(方法名,參數列表相同),則說子類重寫了父類的方法。

註:1.方法名相同 2.參數列表相同 3 子類重寫方法的訪問修飾符必須大於等於父類的方法。(比如父類為 protected void print(){},則子類必須protected或者public)

來看一下重寫的例子:

public class Main {
    public static void main(String[] args) {
        Father son = new Son();
        son.print();
    }
}
class Father{public void print() {
        System.out.println("father");
    }
}
class Son extends Father{public void print() {
        System.out.println(
"son"); } }

結果很明顯:son

看一下字節碼:

技術分享圖片

從第9行可以看到:invokevirtual指令的註釋顯示了是Father.print()的符號引用。但是為什麽結果是son而不是father呢。

首先要說關於靜態類型和實際類型:

Father son = new Son(); 中

Father稱為靜態類型,而Son稱為實際類型。區別在於靜態類型在編譯期就會確定,而實際類型要在運行期才能確定。編譯器編譯程序時候並不知道對象的實際類型是什麽。

所以由於重載在編譯期就確定了類型。所以這個過程也可以叫靜態分派。而重寫是動態分派。

來看一下invokevirtual指令的解析過程(參考《深入理解Java虛擬機》):

1.找到操作數棧頂的第一個元素所指向的對象的實際類型。記作C(在上面代碼實際類型為Son)

2.如果在類型C中找到與常量中描述符合簡單名稱都相符的方法,則進行訪問權限校驗,如果通過則返回這個方法的直接引用。查找過程結束。不通過的話則返回java.lang.IllegalAccessError異常

(上面代碼指的就是查找print()方法,在Son中找到了。所以就成功結束查找過程)

3.否則,按照繼承關系從下往上一次對C的各個父類進行第2步的搜索和驗證過程。

4.如果始終沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常。

由於invokevirtual指令執行的第一步就是在運行期確定實際類型。所以invokevirtual指令把常量池的方法的類方法符號引用(即print())解析到了Son的直接引用中。所以我們具體看到的結果就是Son.print()。

這就是重寫的本質。

關於Father f = new Son() 中 f的方法問題。

在題目中經常會看到關於下面的題目:

public class Main {
    public static void main(String[] args) {
        Father son = new Son();
        son.print();
    }
}
class Father{
public void print() { System.out.println("father"); } } class Son extends Father{
public void printSon() { System.out.println("son"); } }

結果大家都能記住:father

這裏就可以用invokevirtual指令的解析順序來解釋:C還是Son類,但是Son中並沒有print()方法,所以往父類找。在父類找到了返回print()方法的直接引用。所以最後調用的是Father.print()。

註:以前看視頻這個是父類引用指向子類的實例對象。向上轉型之類的,列舉一堆例子概念讓人記住結果而已。反正我現在已經不怎麽記得了。

當從invokevirtual指令解析順序去理解,其實整個過程會變得清晰多了。

面向對象基本特征:多態