1. 程式人生 > >JAVA程式設計思想學習筆記(七)多型

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)。