JAVA程式設計思想學習筆記(七)多型
多型
繫結
繫結: 將一個方法呼叫同一個方法主體關聯起來被稱作繫結。
前期繫結: 若在程式執行前進行繫結,叫做前期繫結,它是面嚮物件語言不需要選擇就預設的繫結方式。
後期繫結: 它的含義就是在執行時根據物件的型別進行繫結,也叫做動態繫結或執行時繫結。java中除了static和final外,都是動態繫結。注:final宣告的方法可以有效的關閉動態繫結,編譯器就可以呼叫更有效的程式碼,但是對於程式整體效能來說,並不會有什麼改觀。
因為是後期繫結,所以在使用時,某一個基類的引用指向了子類的物件,呼叫的還是子類的方法,而不是基類的。
下面看一種情況:
public class TestA { int i; private void fun(){System.out.println("A");} public static void main(String[] args) { // TODO Auto-generated method stub TestA a=new TestB(); a.fun(); } } public class TestB extends TestA{ public void fun(){System.out.println("B");} }
我們可能會想當然的認為輸出是B,因為指向的物件時B,但是結果是A,由於private方法自動認為是final,而且對匯出類是遮蔽的,因此,B中的fun方法是一個全新的方法,基類中的fun不能被過載,因此呼叫的還是基類中的fun方法。
下面改下程式碼,再猜猜結果:
public class TestA { int i=1; void fun(){System.out.println("A"+i);} public static void main(String[] args) { // TODO Auto-generated method stub TestA a=new TestB(); a.fun(); System.out.println(a.i); } } public class TestB extends TestA{ int i=2; public void fun(){System.out.println("B"+i);} }
會輸出什麼呢?答案是
B2
1
為啥方法裡面呼叫的就是子類的,而直接用就是基類的?因為任何域訪問操作都將由編譯器解析,因此不是多型的。
構造器
如果在構造器中使用多型方法會怎麼樣呢?
在一般的方法內部,動態繫結是執行時才決定的,如果要呼叫構造器內部的一個動態繫結方法,就要用到被覆蓋後的定義,然而,被覆蓋的方法,在被完全構造之前就會被呼叫,這可能造成隱藏的錯誤。
下面看段程式碼:
public class TestA { int i=1; void fun(){System.out.println("A"+i);} TestA(){ System.out.println("fun before"); fun(); System.out.println("fun after"); } } public class TestB extends TestA{ int i=2; public void fun(){System.out.println("B"+i);} TestB(int a){ i=a; System.out.println("B end"); } public static void main(String[] args) { // TODO Auto-generated method stub new TestB(5); } }
其輸出結果為:
fun before
B0
fun after
B end
看其結果,可以知道,在構造器被呼叫時,先呼叫的基類的構造器,在構造器中呼叫方法時,過載子類的方法,但是輸出的“i”值既不是1也不是2,而是0,這是為什麼呢?
因為在其他任何事物發生之前,將分配給物件的儲存空間初始化為二進位制的零,所以呼叫構造器時,i的值還沒賦給新值,而是初始化的零。
所以編寫構造器時有一條有效的準則:用盡可能簡單的方法使物件進入正常的狀態,如果可以的話避免呼叫其他方法。
向下轉型
由於向上轉型會丟失具體的型別資訊,所以出現了向下轉型,由於向下轉型,我們無法知道一個“動物”他是“貓”是“狗”還是“豬”,所以必須有某種方法確定轉型的正確性。
在java中所有轉型都會得到檢查,這種執行期間對型別進行檢查行為稱作“執行時型別識別”(RTTI)。