多態(tài)冠胯,大概每個(gè)人都知道。但是锦针,又有幾個(gè)人真的理解什么是多態(tài)荠察、多態(tài)有哪些細(xì)節(jié)呢置蜀?如果你看到這篇文章的名字,腦海中對(duì)多態(tài)沒有一個(gè)清晰的概念悉盆,不妨點(diǎn)進(jìn)來看看盯荤,也許會(huì)有收獲。
什么是多態(tài)
簡(jiǎn)單的理解多態(tài)
多態(tài)焕盟,簡(jiǎn)而言之就是同一個(gè)行為具有多個(gè)不同表現(xiàn)形式或形態(tài)的能力秋秤。比如說,有一杯水脚翘,我不知道它是溫的灼卢、冰的還是燙的,但是我一摸我就知道了来农。我摸水杯這個(gè)動(dòng)作鞋真,對(duì)于不同溫度的水,就會(huì)得到不同的結(jié)果沃于。這就是多態(tài)涩咖。
那么,java中是怎么體現(xiàn)多態(tài)呢繁莹?我們來直接看代碼:
public class Water {
public void showTem(){
System.out.println("我的溫度是: 0度");
}
}
public class IceWater extends Water {
public void showTem(){
System.out.println("我的溫度是: 0度");
}
}
public class WarmWater extends Water {
public void showTem(){
System.out.println("我的溫度是: 40度");
}
}
public class HotWater extends Water {
public void showTem(){
System.out.println("我的溫度是: 100度");
}
}
public class TestWater{
public static void main(String[] args) {
Water w = new WarmWater();
w.showTem();
w = new IceWater();
w.showTem();
w = new HotWater();
w.showTem();
}
}
//結(jié)果:
//我的溫度是: 40度
//我的溫度是: 0度
//我的溫度是: 100度
這里的方法showTem()就相當(dāng)于你去摸水杯檩互。我們定義的water類型的引用變量w就相當(dāng)于水杯,你在水杯里放了什么溫度的水咨演,那么我摸出來的感覺就是什么闸昨。就像代碼中的那樣,放置不同溫度的水雪标,得到的溫度也就不同零院,但水杯是同一個(gè)溉跃。
想必你也看出來了村刨,這段代碼中最關(guān)鍵的就是這一句
Water w = new WarmWater();
看過我前幾篇文章的應(yīng)該知道,我說在講多態(tài)的時(shí)候撰茎,會(huì)講一個(gè)很重要的知識(shí)點(diǎn) --- 向上轉(zhuǎn)型嵌牺。
這句代碼體現(xiàn)的就是向上轉(zhuǎn)型。后面我會(huì)詳細(xì)講解這一知識(shí)點(diǎn)龄糊。
多態(tài)的分類
已經(jīng)簡(jiǎn)單的認(rèn)識(shí)了多態(tài)了逆粹,那么我們來看一下多態(tài)的分類。
多態(tài)一般分為兩種:重寫式多態(tài)和重載式多態(tài)炫惩。重寫和重載這兩個(gè)知識(shí)點(diǎn)前面的文章已經(jīng)詳細(xì)將結(jié)果了僻弹,這里就不多說了。
-
重載式多態(tài)他嚷,也叫編譯時(shí)多態(tài)蹋绽。也就是說這種多態(tài)再編譯時(shí)已經(jīng)確定好了芭毙。重載大家都知道,方法名相同而參數(shù)列表不同的一組方法就是重載卸耘。在調(diào)用這種重載的方法時(shí)退敦,通過傳入不同的參數(shù)最后得到不同的結(jié)果。
但是這里是有歧義的蚣抗,有的人覺得不應(yīng)該把重載也算作多態(tài)侈百。因?yàn)楹芏嗳藢?duì)多態(tài)的理解是:程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時(shí)并不確定,而是在程序運(yùn)行期間才確定翰铡,這種情況叫做多態(tài)钝域。 這個(gè)定義中描述的就是我們的第二種多態(tài)---重寫式多態(tài)。并且两蟀,重載式多態(tài)并不是面向?qū)ο缶幊烫赜械耐遥鄳B(tài)卻是面向?qū)ο笕筇匦灾唬ㄈ绻艺f的不對(duì),記得告訴我赂毯。战虏。)。
我覺得大家也沒有必要在定義上去深究這些党涕,我的理解是:同一個(gè)行為具有多個(gè)不同表現(xiàn)形式或形態(tài)的能力就是多態(tài)烦感,所以我認(rèn)為重載也是一種多態(tài),如果你不同意這種觀點(diǎn)膛堤,我也接受手趣。
-
重寫式多態(tài),也叫運(yùn)行時(shí)多態(tài)肥荔。這種多態(tài)通過動(dòng)態(tài)綁定(dynamic binding)技術(shù)來實(shí)現(xiàn)绿渣,是指在執(zhí)行期間判斷所引用對(duì)象的實(shí)際類型,根據(jù)其實(shí)際的類型調(diào)用其相應(yīng)的方法燕耿。也就是說中符,只有程序運(yùn)行起來,你才知道調(diào)用的是哪個(gè)子類的方法誉帅。
這種多態(tài)通過函數(shù)的重寫以及向上轉(zhuǎn)型來實(shí)現(xiàn)淀散,我們上面代碼中的例子就是一個(gè)完整的重寫式多態(tài)。我們接下來講的所有多態(tài)都是重寫式多態(tài)蚜锨,因?yàn)樗攀敲嫦驅(qū)ο缶幊讨姓嬲亩鄳B(tài)档插。動(dòng)態(tài)綁定技術(shù)涉及到j(luò)vm,暫時(shí)不講(因?yàn)槲乙膊欢窃伲┕牛信d趣的可以自己去研究一下,我暫時(shí)還沒有時(shí)間去研究jvm氛悬。则剃。
多態(tài)的條件
前面說過凄诞,我們接下來說的多態(tài),都是運(yùn)行時(shí)多態(tài)忍级。
- 繼承帆谍。在多態(tài)中必須存在有繼承關(guān)系的子類和父類。
- 重寫轴咱。子類對(duì)父類中某些方法進(jìn)行重新定義汛蝙,在調(diào)用這些方法時(shí)就會(huì)調(diào)用子類的方法。
- 向上轉(zhuǎn)型朴肺。在多態(tài)中需要將子類的引用賦給父類對(duì)象窖剑,只有這樣該引用才能夠具備技能調(diào)用父類的方法和子類的方法。
繼承也可以替換為實(shí)現(xiàn)接口戈稿。
繼承和重寫之前都說過了西土,接下來我們來看一下轉(zhuǎn)型是什么。
向上轉(zhuǎn)型與向下轉(zhuǎn)型
向上轉(zhuǎn)型
子類引用的對(duì)象轉(zhuǎn)換為父類類型稱為向上轉(zhuǎn)型鞍盗。通俗地說就是是將子類對(duì)象轉(zhuǎn)為父類對(duì)象需了。此處父類對(duì)象可以是接口。
案例驅(qū)動(dòng)
看一個(gè)大家都知道的例子:
public class Animal {
public void eat(){
System.out.println("animal eatting...");
}
}
public class Cat extends Animal{
public void eat(){
System.out.println("我吃魚");
}
}
public class Dog extends Animal{
public void eat(){
System.out.println("我吃骨頭");
}
public void run(){
System.out.println("我會(huì)跑");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Cat(); //向上轉(zhuǎn)型
animal.eat();
animal = new Dog();
animal.eat();
}
}
//結(jié)果:
//我吃魚
//我吃骨頭
這就是向上轉(zhuǎn)型般甲,Animal animal = new Cat();將子類對(duì)象Cat轉(zhuǎn)化為父類對(duì)象Animal肋乍。這個(gè)時(shí)候animal這個(gè)引用調(diào)用的方法是子類方法。
關(guān)于方法調(diào)用的順序敷存,我們后面會(huì)詳細(xì)講解墓造。
轉(zhuǎn)型過程中需要注意的問題
- 向上轉(zhuǎn)型時(shí),子類單獨(dú)定義的方法會(huì)丟失锚烦。比如上面Dog類中定義的run方法觅闽,當(dāng)animal引用指向Dog類實(shí)例時(shí)是訪問不到run方法的,
animal.run()
會(huì)報(bào)錯(cuò)涮俄。 - 子類引用不能指向父類對(duì)象蛉拙。
Cat c = (Cat)new Animal()
這樣是不行的。
向上轉(zhuǎn)型的好處
- 減少重復(fù)代碼禽拔,使代碼變得簡(jiǎn)潔刘离。
- 提高系統(tǒng)擴(kuò)展性室叉。
舉個(gè)例子:比如我現(xiàn)在有很多種類的動(dòng)物睹栖,要喂它們吃東西。如果不用向上轉(zhuǎn)型茧痕,那我需要這樣寫:
public void eat(Cat c){
c.eat();
}
public void eat(Dog d){
d.eat();
}
//......
eat(new Cat());
eat(new Cat());
eat(new Dog());
//......
一種動(dòng)物寫一個(gè)方法野来,如果我有一萬種動(dòng)物,我就要寫一萬個(gè)方法踪旷,寫完大概猴年馬月都過了好幾個(gè)了吧曼氛。好吧豁辉,你很厲害,你耐著性子寫完了舀患,以為可以放松一會(huì)了徽级,突然又來了一種新的動(dòng)物,你是不是又要單獨(dú)為它寫一個(gè)eat方法聊浅?開心了么餐抢?
那如果我使用向上轉(zhuǎn)型呢?我只需要這樣寫:
public void eat(Animal a){
a.eat();
}
eat(new Cat());
eat(new Cat());
eat(new Dog());
//.....
恩低匙,搞定了旷痕。代碼是不是簡(jiǎn)潔了許多?而且這個(gè)時(shí)候顽冶,如果我又有一種新的動(dòng)物加進(jìn)來欺抗,我只需要實(shí)現(xiàn)它自己的類,讓他繼承Animal就可以了强重,而不需要為它單獨(dú)寫一個(gè)eat方法绞呈。是不是提高了擴(kuò)展性?
向下轉(zhuǎn)型
與向上轉(zhuǎn)型相對(duì)應(yīng)的就是向下轉(zhuǎn)型了间景。向下轉(zhuǎn)型是把父類對(duì)象轉(zhuǎn)為子類對(duì)象报强。(請(qǐng)注意!這里是有坑的拱燃。)
案例驅(qū)動(dòng)
先看一個(gè)例子:
//還是上面的animal和cat dog
Animal a = new Cat();
Cat c = ((Cat) a);
c.eat();
//輸出 我吃魚
Dog d = ((Dog) a);
d.eat();
// 報(bào)錯(cuò) : java.lang.ClassCastException:com.chengfan.animal.Cat cannot be cast to com.chengfan.animal.Dog
Animal a1 = new Animal();
Cat c1 = ((Cat) a1);
c1.eat();
// 報(bào)錯(cuò) : java.lang.ClassCastException:com.chengfan.animal.Animal cannot be cast to com.chengfan.animal.Cat
為什么第一段代碼不報(bào)錯(cuò)呢秉溉?相比你也知道了,因?yàn)閍本身就是Cat對(duì)象碗誉,所以它理所當(dāng)然的可以向下轉(zhuǎn)型為Cat召嘶,也理所當(dāng)然的不能轉(zhuǎn)為Dog,你見過一條狗突然就變成一只貓這種操蛋現(xiàn)象哮缺?
而a1為Animal對(duì)象弄跌,它也不能被向下轉(zhuǎn)型為任何子類對(duì)象。比如你去考古尝苇,發(fā)現(xiàn)了一個(gè)新生物铛只,知道它是一種動(dòng)物,但是你不能直接說糠溜,啊淳玩,它是貓,或者說它是狗非竿。
向下轉(zhuǎn)型注意事項(xiàng)
向下轉(zhuǎn)型的前提是父類對(duì)象指向的是子類對(duì)象(也就是說蜕着,在向下轉(zhuǎn)型之前轮傍,它得先向上轉(zhuǎn)型)
-
向下轉(zhuǎn)型只能轉(zhuǎn)型為本類對(duì)象(貓是不能變成狗的)壮吩。
大概你會(huì)說磕蒲,我特么有病啊稠屠,我先向上轉(zhuǎn)型再向下轉(zhuǎn)型?韧骗?
我們回到上面的問題:喂動(dòng)物吃飯嘉抒,吃了飯做點(diǎn)什么呢?不同的動(dòng)物肯定做不同的事袍暴,怎么做呢众眨?
public void eat(Animal a){
if(a instanceof Dog){
Dog d = (Dog)a;
d.eat();
d.run();//狗有一個(gè)跑的方法
}
if(a instanceof Cat){
Cat c = (Cat)a;
c.eat();
System.out.println("我也想跑,但是不會(huì)"); //貓會(huì)抱怨
}
a.eat();//其他動(dòng)物只會(huì)吃
}
eat(new Cat());
eat(new Cat());
eat(new Dog());
//.....
現(xiàn)在容诬,你懂了么娩梨?這就是向下轉(zhuǎn)型的簡(jiǎn)單應(yīng)用,可能舉得例子不恰當(dāng)览徒,但是也可以說明一些問題狈定。
敲黑板,劃重點(diǎn)习蓬!看到那個(gè)instanceof了么纽什?
經(jīng)典案例分析多態(tài)
基本的多態(tài)和轉(zhuǎn)型我們都會(huì)了,最后加點(diǎn)餐躲叼÷郑看一個(gè)經(jīng)典案例:
class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
class C extends B{
}
class D extends B{
}
public class Demo {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
//結(jié)果:
//1--A and A
//2--A and A
//3--A and D
//4--B and A
//5--B and A
//6--A and D
//7--B and B
//8--B and B
//9--A and D
//能看懂這個(gè)結(jié)果么?先自分析一下枫慷。
前三個(gè)让蕾,強(qiáng)行分析,還能看得懂或听。但是第四個(gè)探孝,大概你就傻了吧。為什么不是b and b呢誉裆?
這里就要學(xué)點(diǎn)新東西了顿颅。
當(dāng)父類對(duì)象引用變量引用子類對(duì)象時(shí),被引用對(duì)象的類型決定了調(diào)用誰的成員方法足丢,引用變量類型決定可調(diào)用的方法粱腻。如果子類中沒有覆蓋該方法,那么會(huì)去父類中尋找斩跌。
可能讀起來比較拗口绍些,我們先來看一個(gè)簡(jiǎn)單的例子:
class X {
public void show(Y y){
System.out.println("x and y");
}
public void show(){
System.out.println("only x");
}
}
class Y extends X {
public void show(Y y){
System.out.println("y and y");
}
public void show(int i){
}
}
class main{
public static void main(String[] args) {
X x = new Y();
x.show(new Y());
x.show();
}
}
//結(jié)果
//y and y
//only x
Y繼承了X,覆蓋了X中的show(Y y)方法滔驶,但是沒有覆蓋show()方法遇革。
這個(gè)時(shí)候卿闹,引用類型為X的x指向的對(duì)象為Y揭糕,這個(gè)時(shí)候萝快,調(diào)用的方法由Y決定,會(huì)先從Y中尋找著角。執(zhí)行x.show(new Y());
揪漩,該方法在Y中定義了,所以執(zhí)行的是Y里面的方法吏口;
但是執(zhí)行x.show();
的時(shí)候奄容,有的人會(huì)說,Y中沒有這個(gè)方法安病昂勒?它好像是去父類中找該方法了,因?yàn)檎{(diào)用了X中的方法舟铜。
事實(shí)上戈盈,Y類中是有show()方法的,這個(gè)方法繼承自X谆刨,只不過沒有覆蓋該方法塘娶,所以沒有在Y中明確寫出來而已,看起來像是調(diào)用了X中的方法痊夭,實(shí)際上調(diào)用的還是Y中的刁岸。
這個(gè)時(shí)候再看上面那句難理解的話就不難理解了吧。X是引用變量類型她我,它決定哪些方法可以調(diào)用虹曙;show()和show(Y y)可以調(diào)用,而show(int i)不可以調(diào)用番舆。Y是被引用對(duì)象的類型根吁,它決定了調(diào)用誰的方法:調(diào)用y的方法。
上面的是一個(gè)簡(jiǎn)單的知識(shí)合蔽,它還不足以讓我們理解那個(gè)復(fù)雜的例子击敌。我們?cè)賮砜催@樣一個(gè)知識(shí):
繼承鏈中對(duì)象方法的調(diào)用的優(yōu)先級(jí):this.show(O)、super.show(O)拴事、this.show((super)O)沃斤、super.show((super)O)。
如果你能理解這個(gè)調(diào)用關(guān)系刃宵,那么多態(tài)你就掌握了衡瓶。我們回到那個(gè)復(fù)雜的例子:
abcd的關(guān)系是這樣的:C/D ---> B ---> A
我們先來分析4 : a2.show(b)
- 首先,a2是類型為A的引用類型牲证,它指向類型為B的對(duì)象哮针。A確定可調(diào)用的方法:show(D obj)和show(A obj)。
a2.show(b)
==>this.show(b)
,這里this指的是B十厢。- 然后.在B類中找show(B obj)等太,找到了,可惜沒用蛮放,因?yàn)閟how(B obj)方法不在可調(diào)用范圍內(nèi)缩抡,
this.show(O)
失敗,進(jìn)入下一級(jí)別:super.show(O)
包颁,super指的是A瞻想。- 在A 中尋找show(B obj),失敗娩嚼,因?yàn)闆]用定義這個(gè)方法蘑险。進(jìn)入第三級(jí)別:
this.show((super)O)
,this指的是B岳悟。- 在B中找show((A)O),找到了:show(A obj)漠其,選擇調(diào)用該方法。
- 輸出:B and A
如果你能看懂這個(gè)過程竿音,并且能分析出其他的情況和屎,那你就真的掌握了。
我們?cè)賮砜匆幌?:b.show(d)
- 首先春瞬,b為類型為B的引用對(duì)象柴信,指向類型為B的對(duì)象。沒有涉及向上轉(zhuǎn)型宽气,只會(huì)調(diào)用本類中的方法随常。
- 在B中尋找show(D obj),方法√蜒模現(xiàn)在你不會(huì)說沒找到了吧绪氛?找到了,直接調(diào)用該方法涝影。
- 輸出 A and D枣察。
總結(jié)
本篇文章的內(nèi)容大體上就是這些了。我們來總結(jié)一下燃逻。
- 多態(tài)序目,簡(jiǎn)而言之就是同一個(gè)行為具有多個(gè)不同表現(xiàn)形式或形態(tài)的能力。
- 多態(tài)的分類:運(yùn)行時(shí)多態(tài)和編譯時(shí)多態(tài)伯襟。
- 運(yùn)行時(shí)多態(tài)的前提:繼承(實(shí)現(xiàn))猿涨,重寫,向上轉(zhuǎn)型
- 向上轉(zhuǎn)型與向下轉(zhuǎn)型姆怪。
- 繼承鏈中對(duì)象方法的調(diào)用的優(yōu)先級(jí):this.show(O)叛赚、super.show(O)澡绩、this.show((super)O)、super.show((super)O)俺附。
事實(shí)上肥卡,當(dāng)你使用向上轉(zhuǎn)型的時(shí)候,你就使用了多態(tài)昙读。關(guān)于多態(tài)還有一些其他的知識(shí)點(diǎn)召调,比如幾個(gè)關(guān)鍵字:static膨桥、final等蛮浑,我們后面會(huì)一一講解。下一篇《重新認(rèn)識(shí)java(六) ----java中的另類:static關(guān)鍵字》不定期更新只嚣。
如果文章內(nèi)容有什么問題沮稚,或者哪里出錯(cuò),請(qǐng)及時(shí)與我聯(lián)系册舞。不保證文章內(nèi)容的完全正確性蕴掏。如果你有更好的簡(jiǎn)潔或者更好的理解方式,歡迎一起交流调鲸。
歡迎轉(zhuǎn)載~轉(zhuǎn)載請(qǐng)注明出處盛杰!
原文地址:
http://www.reibang.com/p/5771df145452
看完了隨手點(diǎn)個(gè)贊唄~