一、面向對象最核心的機制——動態(tài)綁定悲敷,也叫多態(tài)
1.1.通過下面的例子理解動態(tài)綁定屎暇,即多態(tài)
package javastudy.summary;
class Animal {
/**
* 聲明一個私有的成員變量name囚巴。
*/
private String name;
/**
* 在Animal類自定義的構造方法
* @param name
*/
Animal(String name) {
this.name = name;
}
/**
* 在Animal類里面自定義一個方法enjoy
*/
public void enjoy() {
System.out.println("動物的叫聲……");
}
}
/**
* 子類Cat從父類Animal繼承下來您炉,Cat類擁有了Animal類所有的屬性和方法柒爵。
* @author gacl
*
*/
class Cat extends Animal {
/**
* 在子類Cat里面定義自己的私有成員變量
*/
private String eyesColor;
/**
* 在子類Cat里面定義Cat類的構造方法
* @param n
* @param c
*/
Cat(String n, String c) {
/**
* 在構造方法的實現(xiàn)里面首先使用super調用父類Animal的構造方法Animal(String name)。
* 把子類對象里面的父類對象先造出來赚爵。
*/
super(n);
eyesColor = c;
}
/**
* 子類Cat對從父類Animal繼承下來的enjoy方法不滿意棉胀,在這里重寫了enjoy方法法瑟。
*/
public void enjoy() {
System.out.println("我養(yǎng)的貓高興地叫了一聲……");
}
}
/**
* 子類Dog從父類Animal繼承下來,Dog類擁有了Animal類所有的屬性和方法唁奢。
* @author gacl
*
*/
class Dog extends Animal {
/**
* 在子類Dog里面定義自己的私有成員變量
*/
private String furColor;
/**
* 在子類Dog里面定義Dog類的構造方法
* @param n
* @param c
*/
Dog(String n, String c) {
/**
* 在構造方法的實現(xiàn)里面首先使用super調用父類Animal的構造方法Animal(String name)霎挟。
* 把子類對象里面的父類對象先造出來。
*/
super(n);
furColor = c;
}
/**
* 子類Dog對從父類Animal繼承下來的enjoy方法不滿意麻掸,在這里重寫了enjoy方法酥夭。
*/
public void enjoy() {
System.out.println("我養(yǎng)的狗高興地叫了一聲……");
}
}
/**
* 子類Bird從父類Animal繼承下來,Bird類擁有Animal類所有的屬性和方法
* @author gacl
*
*/
class Bird extends Animal {
/**
* 在子類Bird里面定義Bird類的構造方法
*/
Bird() {
/**
* 在構造方法的實現(xiàn)里面首先使用super調用父類Animal的構造方法Animal(String name)脊奋。
* 把子類對象里面的父類對象先造出來采郎。
*/
super("bird");
}
/**
* 子類Bird對從父類Animal繼承下來的enjoy方法不滿意,在這里重寫了enjoy方法狂魔。
*/
public void enjoy() {
System.out.println("我養(yǎng)的鳥高興地叫了一聲……");
}
}
/**
* 定義一個類Lady(女士)
* @author gacl
*
*/
class Lady {
/**
* 定義Lady類的私有成員變量name和pet
*/
private String name;
private Animal pet;
/**
* 在Lady類里面定義自己的構造方法Lady(),
* 這個構造方法有兩個參數(shù)淫痰,分別為String類型的name和Animal類型的pet最楷,
* 這里的第二個參數(shù)設置成Animal類型可以給我們的程序帶來最大的靈活性,
* 因為作為養(yǎng)寵物來說待错,可以養(yǎng)貓籽孙,養(yǎng)狗,養(yǎng)鳥火俄,只要是你喜歡的都可以養(yǎng)犯建,
* 因此把它設置為父類對象的引用最為靈活。
* 因為這個Animal類型的參數(shù)是父類對象的引用類型瓜客,因此當我們傳參數(shù)的時候适瓦,
* 可以把這個父類的子類對象傳過去,即傳Dog谱仪、Cat和Bird等都可以玻熙。
* @param name
* @param pet
*/
Lady(String name, Animal pet) {
this.name = name;
this.pet = pet;
}
/**
* 在Lady類里面自定義一個方法myPetEnjoy()
* 方法體內是讓Lady對象養(yǎng)的寵物自己調用自己的enjoy()方法發(fā)出自己的叫聲。
*/
public void myPetEnjoy() {
pet.enjoy();
}
}
public class TestPolymoph {
public static void main(String args[]) {
/**
* 在堆內存里面new了一只藍貓對象出來疯攒,這個藍貓對象里面包含有一個父類對象Animal嗦随。
*/
Cat c = new Cat("Catname", "blue");
/**
* 在堆內存里面new了一只黑狗對象出來,這個黑狗對象里面包含有一個父類對象Animal敬尺。
*/
Dog d = new Dog("Dogname", "black");
/**
* 在堆內存里面new了一只小鳥對象出來枚尼,這個小鳥對象里面包含有一個父類對象Animal。
*/
Bird b = new Bird();
/**
* 在堆內存里面new出來3個小姑娘砂吞,名字分別是l1署恍,l2,l3呜舒。
* l1養(yǎng)了一只寵物是c(Cat)锭汛,l2養(yǎng)了一只寵物是d(Dog)笨奠,l3養(yǎng)了一只寵物是b(Bird)。
* 注意:調用Lady類的構造方法時唤殴,傳遞過來的c般婆,d,b是當成Animal來傳遞的朵逝,
* 因此使用c蔚袍,d,b這三個引用對象只能訪問父類Animal里面的enjoy()方法配名。
*/
Lady l1 = new Lady("l1", c);
Lady l2 = new Lady("l2", d);
Lady l3 = new Lady("l3", b);
/**
* 這三個小姑娘都調用myPetEnjoy()方法使自己養(yǎng)的寵物高興地叫起來啤咽。
*/
l1.myPetEnjoy();
l2.myPetEnjoy();
l3.myPetEnjoy();
}
}
運行結果:
1.2.畫內存圖理解動態(tài)綁定(多態(tài))
首先從main方法的第一句話開始分析:
Cat c = new Cat("Catname","blue");
程序執(zhí)行到這里,椙觯空間里有一個變量c宇整,c里面裝著一系列的值,通過這些值可以找到位于堆內存里面new出來的Cat對象芋膘。因此c是Cat對象的一個引用鳞青,通過c可以看到這個Cat對象的全部。c指向new出來的Cat對象为朋。在new這個Cat對象的時候臂拓,調用了Cat對象的構造方法Cat(String n,String c),定義如下:
Cat(String n,String c){
super(n);
eyesColor=c;
}
因此在構造子類對象時首先使用父類對象的引用super調用父類的構造方法Animal(String name),定義如下:
Animal(String name){
this.name=name;
}
因此會把傳過來的字符串“Catname”傳遞給父類對象的name屬性习寸。當Cat(String n,String c)構造方法調用結束后胶惰,真真正正在堆內存里面new出了一只Cat,這只Cat里面包含有父類對象Animal霞溪,這個Animal對象有自己的屬性name孵滞,name屬性的值為調用父類構造方法時傳遞過來的字符串Catname。除此之外鸯匹,這只Cat還有自己的私有成員變量eyesColor剃斧,eyesColor屬性的屬性值為調用子類構造方法時傳遞過來的字符串blue。所以執(zhí)行完這句話以后忽你,內存中的布局是棧內存里面有一個引用c幼东,c指向堆內存里面new出來的一只Cat,而這只Cat對象里面又包含有父類對象Animal科雳,Animal對象有自己的屬性name根蟹,屬性值為Catname,Cat除了擁有從Animal類繼承下來的name屬性外糟秘,還擁有一個自己私有的屬性eyesColor简逮,屬性值為blue。這就是執(zhí)行完第一句話以后整個內存布局的情況如下圖所示:
接著看這句話:Lady l1 = new Lady(“l(fā)1”,c);
程序執(zhí)行到這里,首先在棧內存里面多了一個引用變量l1,l1里面裝著一個值尿赚,通過這個值可以找到在堆內存里面new出來的Lady對象散庶。l1就是這個Lady對象的引用蕉堰,l1指向Lady對象。在創(chuàng)建Lady對象時悲龟,調用Lady類的構造方法:Lady(String name,Animal pet)屋讶,其定義如下:
Lady(String name,Animal pet){
this.name=name;
this.pet=pet;
}
這個構造方法有兩個參數(shù),分別是String類型的name和Animal類型的pet须教,pet參數(shù)是一個父類對象的引用類型皿渗,這里把l1和c作為實參傳遞給了構造方法,接著在構造方法里面執(zhí)行this.name=name轻腺,把傳遞過來的l1由傳給Lady對象的name屬性乐疆,因此Lady對象的name屬性值為l1,這里也把前面new出來的那只Cat的引用c傳遞給了構造方法里面的參數(shù)pet贬养,接著在構造方法里面執(zhí)行this.pet=pet挤土,pet參數(shù)又把c傳過來的內容傳遞給Lady對象的pet屬性,因此pet屬性的屬性值就是可以找到Cat對象的地址误算,因此Lady對象的pet屬性也成為了Cat對象的引用對象了耕挨,通過pet里面裝著的值是可以找到Cat對象的,因此pet也指向了Cat尉桩,但并不是全部指向Cat,pet指向的只是位于Cat對象內部的Animal對象贪庙,這是因為在調用構造方法時蜘犁,是把c當成一個Animal對象的引用傳過來的,把c作為一個Animal對象傳遞給了pet止邮,所以得到的pet也是一個Animal對象的引用这橙,因此這個pet引用指向的只能是位于Cat對象里面的Animal對象。在我pet引用對象眼里导披,你Cat對象就是一只普通的Animal屈扎,訪問你的時候只能訪問得到你里面的name屬性,而你的eyesColor屬性我是訪問不到的撩匕,我能訪問到你的name屬性鹰晨,訪問的是位于你內部里面的父對象的name屬性,因為我pet引用本身就是一個父類對象的引用止毕,因此我可以訪問父類對象的全部屬性模蜡,而你子類對象Cat自己新增加的成員我pet引用是訪問不了的。不過現(xiàn)在我pet引用不去訪問你父類對象的成員變量name了扁凛,而是去訪問你的成員方法enjoy了忍疾。首先是使用Lady對象的引用l1去調用Lady對象的myPetEnjoy()方法,myPetEnjoy()方法定義如下:
public void myPetEnjoy(){
pet.enjoy();
}
然后在myPetEnjoy()方法體里面又使用pet引用對象去調用父類對象里面的enjoy方法谨朝。
方法是放在代碼區(qū)(code seg)里面的卤妒,里面的方法就是一句句代碼甥绿。因此當使用pet****引用去訪問父類對象的方法時,首先是找到這個父類對象则披,然后看看它里面的方法到底在哪里存著共缕,找到那個方法再去執(zhí)行。這里頭就比較有意思了收叶,code seg里面有很多個enjoy方法骄呼,有父類的enjoy()方法,也有子類重寫了從父類繼續(xù)下來的enjoy()方法判没,那么調用的時候到底調用的是哪一個呢蜓萄?是根據(jù)誰來確定呢?注意:這是根據(jù)你實際當中的對象來確定的澄峰,你實際當中new出來的是誰嫉沽,就調用誰的enjoy方法,當你找這個方法的時候俏竞,通過pet引用能找得到這個方法绸硕,但調用代碼區(qū)里面的哪一個enjoy方法不是通過引用類型來確定的,如果是通過引用類型pet來確定魂毁,那么調用的肯定是Animal的enjoy()方法玻佩,可是現(xiàn)在是根據(jù)實際的類型來確定,我們的程序運行以后才在堆內存里面創(chuàng)建出一只Cat席楚,然后根據(jù)你實際當中new出來的類型來判斷我到底應該調用哪一個enjoy()方法咬崔。如果是根據(jù)實際類型,那么調用的就應該是Cat的enjoy()方法烦秩。如果是根據(jù)引用類型垮斯,那么調用的就應該是Animal的enjoy()方法。現(xiàn)在動態(tài)綁定這種機制指的是實際當中new的是什么類型只祠,就調用誰的enjoy方法兜蠕。所以說雖然你是根據(jù)我父類里面的enjoy方法來調用,可是實際當中卻是你new的是誰調用的就是誰的enjoy()方法抛寝。即實際當中調用的卻是子類里面重寫后的那個enjoy方法熊杨。當然,講一點更深的機制盗舰,你實際當中找這個enjoy方法的時候猴凹,在父類對象的內部有一個enjoy方法的指針,指針指向代碼區(qū)里面父類的Animal的enjoy方法岭皂,只不過當你new這個對象的時候郊霎,這個指針隨之改變,你new的是什么對象爷绘,這個指針就指向這個對象重寫后的那個enjoy方法书劝,所以這就叫做動態(tài)綁定进倍。只有在動起來的時候,也就是在程序運行期間购对,new出了這個對象了以后你才能確定到底要調用哪一個方法猾昆。我實際當中的地址才會綁定到相應的方法的地址上面,所以叫動態(tài)綁定骡苞。調這個方法的時候垂蜗,只要你這個方法重寫了,實際當中調哪一個解幽,要看你實際當中new的是哪個對象贴见,這就叫多態(tài),也叫動態(tài)綁定躲株。動態(tài)綁定帶來莫大的好處是使程序的可擴展性達到了最好片部,我們原來做這個可擴展性的時候,首先都是要在方法里面判斷一下這只動物是哪一類里面的動物霜定,通過if (object instanceof class)這樣的條件來判斷這個new出來的對象到底是屬于哪一個類里面的档悠,如果是一只貓,就調用貓的enjoy方法望浩,如果是一條狗辖所,就調用狗的enjoy方法。如果我現(xiàn)在增加了一個Bird類磨德,那么擴展的時候缘回,你又得在方法里面寫判斷這只鳥屬于哪一個類然后才能調用這只鳥的enjoy方法。每增加一個對象剖张,你都要在方法里面增加一段判斷這個對象到底屬于哪個類里面的代碼然后才能執(zhí)行這個對象相應的方法。即每增加一個新的對象揩环,都要改變方法里面的處理代碼搔弄,而現(xiàn)在,你不需要再改變方法里面的處理代碼了丰滑,因為有了動態(tài)綁定顾犹。你要增加哪一個對象,你實際當中把這個對象new出來就完了褒墨,不再用去修改對象的處理方法里面的代碼了炫刷。也就是當你實際當中要增加別的東西的時候,很簡單郁妈,你直接加上去就成了浑玛,不用去改原來的結構,你要在你們家大樓的旁邊蓋一個廚房噩咪,很簡單顾彰,直接在旁邊一蓋就行了极阅,大樓的主要支柱什么的你都不用動,這就可以讓可擴展性達到了極致涨享,這就為將來的可擴展打下了基礎筋搏,也只有動態(tài)綁定(多態(tài))這種機制能幫助我們做到這一點——讓程序的可擴展性達到極致。因此動態(tài)綁定是面向對象的核心厕隧,如果沒有動態(tài)綁定奔脐,那么面向對象絕對不可能發(fā)展得像現(xiàn)在這么流行,所以動態(tài)綁定是面向對象核心中的核心吁讨。
總結動態(tài)綁定(多態(tài)):動態(tài)綁定是指在“執(zhí)行期間”(而非編譯期間)判斷所引用的實際對象類型髓迎,根據(jù)其實際的類型調用其相應的方法。所以實際當中找要調用的方法時是動態(tài)的去找的挡爵,new的是誰就找誰的方法竖般,這就叫動態(tài)綁定。動態(tài)綁定幫助我們的程序的可擴展性達到了極致茶鹃。
多態(tài)的存在有三個必要的條件:
- 要有繼承(兩個類之間存在繼承關系涣雕,子類繼承父類)
- 要有重寫(在子類里面重寫從父類繼承下來的方法)
- 父類引用指向子類對象
這三個條件一旦滿足,當你調用父類里面被重寫的方法的時候闭翩,實際當中new的是哪個子類對象挣郭,就調用子類對象的方法(這個方法是從父類繼承下來后重寫后的方法)。
面向對象比較強調類和類之間疗韵,對象和對象之間的一種組織關系兑障,如果能把這種組織關系組織得比較好的話,你的程序想擴展性比較好蕉汪,比較健壯流译,維護性比較好這些都可以達到,關鍵看你的設計到底好還是不好者疤。