面向對象基本特征:多態
多態是面向對象最重要的特征。具體到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指令解析順序去理解,其實整個過程會變得清晰多了。
面向對象基本特征:多態