標籤:java 多態 後期綁定
Java中將一個方法調用同一個方法主體關聯起來被稱作綁定。綁定分為前期綁定和後期綁定。前期綁定是在編譯器決定的,而後期綁定是在程式運行時決定的。Java中除了static方法和final方法(private方法也是final方法,只不過是隱式的為final)之外,其他所有的方法都是後期綁定。Java類的多態指的是當將子類向上轉型為父類型並調用父類型中相應的方法時,多態機制會根據動態綁定自動判斷出調用相應的子類的方法。也就是說多態機制的存在的基礎是子類實現了對父類中相應方法的函數覆蓋。比如有一個Shape類,該類有一個draw方法,並有Circle、Triangle、Square這三個類均繼承自Shape類,並且都重寫了父類的draw方法,代碼如下所示:
class Shape{void draw(){System.out.println("Draw Shape");}}class Circle extends Shape{void draw(){System.out.println("Draw Circle");}}class Triangle extends Shape{void draw(){System.out.println("Draw Triangle");}}class Square extends Shape{void draw(){System.out.println("Draw Square");}}public class Test { public static void main(String[] args) { Shape[] shapes = {new Circle(), new Triangle(), new Square()}; for(Shape s : shapes){ s.draw(); } }}
輸出結果為:
Draw Circle
Draw Triangle
Draw Square
我們建立了一個Shapes數組,裡面分別是Circle、Triangle和Square的執行個體,我們在遍曆該Shapes數組時其實已經對其進行了向上轉型,即已經模糊了其實際類型,在遍曆Shapes數組時,只知道每個元素都是一個Shape類型,然後依次調用元素的draw方法。結果沒有調用基類Shape的draw方法,而是調用的對象實際子類型中的darw方法,這種現象就稱之為多態。那此處的多態究竟是怎麼發生的呢?前面說過只要類中的方法不是static和final的,那麼該方法是後期綁定也就是在運行時才決定調用主體。在執行s.draw()這句代碼時,Java知道了要調用darw方法了,s雖然表面看起來是Shape類型,但是它能判斷出s實際上是一個Circle/Triangle/Square類型,這樣就將方法的調用主體設定為更為具體的子類,這樣就執行了具體子類的draw方法而非父類的draw方法。
我們在看如下一段Java代碼:
class Shape{void draw(){System.out.println("Draw Shape");}void show(){draw();}}class Circle extends Shape{void draw(){System.out.println("Draw Circle");}}public class Test { public static void main(String[] args) { Shape s = new Circle(); s.show(); }}執行結果為:Draw Circle
基類Shape中新增了一個show方法,在show方法中會調用draw方法,當執行代碼Shape s = new Circle()時,我們建立了一個Circle類型的執行個體,並將其向上轉型為Shape類型,然後調用基類的show方法,結果基類Shape的show方法調用了子類Circle的draw方法而非基類Shape的draw方法。出現這種情況的原因還是多態機制。當執行基類Shape中的show方法時,show方法內部要執行darw方法,darw方法是要執行的方法名,由於draw方法既不是static的,又不是final的,所以draw方法是後期綁定,也就是在運行時判斷調用主體。Java知道s實際上式Circle類型的執行個體,所以會在基類Shape的show方法中會將子類Circle作為調用主體去調用子類Circle中的draw方法而非基類Shape的draw方法。
那麼我們再對上面的例子進行一處修改,我們將基類Shape的draw方法設定為private私人的,代碼如下:
class Shape{private void draw(){System.out.println("Draw Shape");}void show(){draw();}}class Circle extends Shape{void draw(){System.out.println("Draw Circle");}}public class Test { public static void main(String[] args) { Shape s = new Circle(); s.show(); }}執行結果為:Draw Shape
我們再來分析一下原因。此處還是將子類Circle類型的執行個體向上轉型為基類Shape,在執行基類Shape的show方法時,show方法內要執行draw方法,由於基類Shape中的draw方法被定義為private私人的,而private修飾的方法都實際是final方法(只不過是隱式地修飾為static),所以基類的draw方法是final的,由於final方法的調用都是前期綁定,也就是final方法的調用是在編譯器決定的。所以此處不會發生後期綁定,從而自然執行了基類的draw方法而非子類Circle的draw方法。也可以這樣認為,在我們編寫完這個Java檔案用IDE對其編譯產生class檔案時,由於private方法的前期綁定特性,編譯器會將Shape中的draw方法的代碼都copy到show方法內部。如下所示:
class Shape{private void draw(){System.out.println("Draw Shape");}void show(){//編譯時間將基類draw方法內的代碼都copy到show方法中System.out.println("Draw Shape");}}
可以這樣理解,在產生的class檔案中在show方法中就抹去了draw,只留下copy過來的基類中的draw代碼。
此處代碼沒有執行多態還有一個原因是,多態機制的基礎是子類對父類進行了函數覆蓋。但是在上面的例子中父類Shape中的draw方法被修飾為private的,子類雖然也有一個draw方法,但是這不屬於函數覆蓋。因為父類中的draw方法為private的,對子類是完全屏蔽的,只有子類覆寫了能夠訪問的父類中的方法時,才存在函數覆蓋一說。所以壓根就不存在子類覆蓋父類中的private方法一說。既然Circle和Shape之間不存在函數覆蓋,那麼在在基類Shape的show方法執行draw時就不存在多態調用了。
還有一點需要說明的是只有普通的方法調用可以是多態的,欄位不是多態的。欄位的訪問操作是前期綁定,由編譯器解析,所以不是多態的。此處就不舉例了。
Java類的多態機制