Java基础篇:封装、继承、多态三大特性
目錄:
一、Java三大特性之:封裝
二、Java三大特性之:繼承
三、Java三大特性之:多態(tài)
一、Java三大特性之:封裝
1、什么是封裝:
????????封裝,就是將數(shù)據(jù)和基于數(shù)據(jù)的操作封裝在一起,數(shù)據(jù)被保護在抽象數(shù)據(jù)類型的內(nèi)部,盡可能地隱藏內(nèi)部的細節(jié),只保留一些對外接口使之與外部發(fā)生聯(lián)系。用戶無需知道對象內(nèi)部的細節(jié),但可以通過該對象對外提供的接口來訪問該對象。
2、使用封裝有幾大好處:
(1)良好的封裝能夠減少耦合。
(2)類內(nèi)部的結(jié)構(gòu)可以自由修改。
(3)可以對成員進行更精確的控制。
(4)隱藏信息,實現(xiàn)細節(jié)。
3、例子:
(1)示例一:
首先我們來看兩個類:Husband.java 、 Wife.java
public class Husband {/** 對屬性的封裝* 一個人的姓名、性別、年齡、妻子都是這個人的私有屬性*/private String name ;private String sex ;private int age ;private Wife wife;/** setter()、getter()是該對象對外開發(fā)的接口*/public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public void setWife(Wife wife) {this.wife = wife;} } public class Wife {private String name;private int age;private String sex;private Husband husband;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public void setAge(int age) {this.age = age;}public void setHusband(Husband husband) {this.husband = husband;}public Husband getHusband() {return husband;}}????????從上面兩個實例我們可以看出Husband里面wife引用是沒有g(shù)etter()的,同時wife的age也是沒有g(shù)etter()方法的。所以封裝把一個對象的屬性私有化,同時提供一些可以被外界訪問的屬性的方法,如果不想被外界方法,我們大可不必提供方法給外界訪問。但是如果一個類沒有提供給外界訪問的方法,那么這個類也沒有什么意義了。比如我們將一個房子看做是一個對象,里面的漂亮的裝飾,如沙發(fā)、電視劇、空調(diào)、茶桌等等都是該房子的私有屬性,但是如果我們沒有那些墻遮擋,是不是別人就會一覽無余呢?沒有一點兒隱私!就是存在那個遮擋的墻,我們既能夠有自己的隱私而且我們可以隨意的更改里面的擺設(shè)而不會影響到其他的。但是如果沒有門窗,一個包裹的嚴嚴實實的黑盒子,又有什么存在的意義呢?所以通過門窗別人也能夠看到里面的風景。所以說門窗就是房子對象留給外界訪問的接口。
????????通過這個我們還不能真正體會封裝的好處。現(xiàn)在我們從程序的角度來分析封裝帶來的好處。如果我們不使用封裝,那么該對象就沒有setter()和getter(),那么Husband類應該這樣寫:
public class Husband {public String name ;public String sex ;public int age ;public Wife wife; }我們應該這樣使用它:
Husband husband = new Husband();husband.age = 30;husband.name = "張三";husband.sex = "男";????????但是哪天如果我們需要修改Husband,例如將age修改為String類型的呢?你只有一處使用了這個類還好,如果你有幾十個甚至上百個這樣地方,你是不是要改到崩潰。如果使用了封裝,我們完全可以不需要做任何修改,只需要稍微改變下Husband類的setAge()方法即可。
public class Husband {/** 對屬性的封裝* 一個人的姓名、性別、年齡、妻子都是這個人的私有屬性*/private String name ;private String sex ;private String age ; /* 改成 String類型的*/private Wife wife;public String getAge() {return age;}public void setAge(int age) {//轉(zhuǎn)換即可this.age = String.valueOf(age);}/** 省略其他屬性的setter、getter **/其他的地方依然那樣引用(husband.setAge(22))保持不變。
?????? 到了這里我們確實可以看出,封裝確實可以使我們?nèi)菀椎匦薷念惖膬?nèi)部實現(xiàn),而無需修改使用了該類的客戶代碼。
(2)示例二:
我們再看這個好處:可以對成員變量進行更精確的控制。
還是那個Husband,一般來說我們在引用這個對象的時候是不容易出錯的,但是有時你迷糊了,寫成了這樣:
Husband husband = new Husband(); husband.age = 300;也許你是因為粗心寫錯了,你發(fā)現(xiàn)了還好,如果沒有發(fā)現(xiàn)那可能會引起一些不必要的麻煩。但是使用封裝我們就可以避免這個問題,我們對age的訪問入口做一些控制(setter)如:
public class Husband {/** 對屬性的封裝* 一個人的姓名、性別、年齡、妻子都是這個人的私有屬性*/private String name ;private String sex ;private int age ; /* 改成 String類型的*/private Wife wife;public int getAge() {return age;}public void setAge(int age) {if(age > 120){System.out.println("ERROR:error age input...."); //提示錯誤信息}else{this.age = age;}}/** 省略其他屬性的setter、getter **/ }????????上面都是對setter方法的控制,其實通過使用封裝我們也能夠?qū)ο蟮某隹谧龀龊芎玫目刂啤@缧詣e我們在數(shù)據(jù)庫中一般都是已1、0方式來存儲的,但是在前臺我們又不能展示1、0,這里我們只需要在getter()方法里面做一些轉(zhuǎn)換即可。
public String getSexName() {if("0".equals(sex)){sexName = "女";}else if("1".equals(sex)){sexName = "男";}else{sexName = "其他";}return sexName;}二、Java三大特性之:繼承
1、什么是繼承:
從這里我們看出,Wife、Husband兩個類除了各自的husband、wife外其余部分全部相同,作為一個想最大限度實現(xiàn)復用代碼的我們是不能夠忍受這樣的重復代碼,如果再來Son、Daughter等等,我們是不是也要這樣寫呢?如果不這樣寫,我們該如何來實現(xiàn)這些類的可復用呢?利用繼承!
從常識中我們知道丈夫、妻子、女兒、兒子們都是人,都有一些共性,有名字、年齡、性別等等,都能夠吃東西、走路、說話等等共同的行為,所以從這里我們可以發(fā)現(xiàn)他們都擁有人的屬性和行為,同時也是從人那里繼承來的這些屬性和行為的。
從上面我們就可以基本了解了繼承的概念了,繼承是使用已存在的類作為基礎(chǔ)建立新類的技術(shù),新類的定義可以增加新的數(shù)據(jù)或新的功能,也可以用父類的功能,但不能選擇性地繼承父類。通過使用繼承我們能夠非常方便地復用以前的代碼,能夠大大的提高開發(fā)的效率。
? ? ? ?對于Wife、Husband使用繼承后,除了代碼量的減少我們還能夠非常明顯的看到他們的關(guān)系。
使用繼承可以將若干個相似的類,抽象出他們共有的行為和屬性并將其定義成一個父類,實現(xiàn)代碼共享,避免重復。在使用繼承的時候,需要注意以下幾個地方:
- (1)子類擁有父類的所有屬性和方法,但是父類對象中的私有屬性和方法,子類是無法訪問到的,只是擁有,但不能使用。
- (2)子類可以擁有自己屬性和方法,即子類可以對父類進行擴展。
- (3)子類可以用自己的方式重寫父類的方法。
?????? 講到繼承一定少不了這三個東西:構(gòu)造器、protected關(guān)鍵字、向上轉(zhuǎn)型。
2、構(gòu)造器:
? ? ? ?對于構(gòu)造器而言,它只能夠被調(diào)用,而不能被繼承。 調(diào)用父類的構(gòu)造方法我們使用super()即可。
?????? 對于子類而已,其構(gòu)造器的正確初始化是非常重要的,而且當且僅當只有一個方法可以保證這點:在構(gòu)造器中調(diào)用父類構(gòu)造器來完成初始化,而父類構(gòu)造器具有執(zhí)行父類初始化所需要的所有知識和能力。
public class Person {protected String name;protected int age;protected String sex;Person(){System.out.println("Person Constrctor...");} }public class Husband extends Person{private Wife wife;Husband(){System.out.println("Husband Constructor...");}public static void main(String[] args) {Husband husband = new Husband();} }Output: Person Constrctor... Husband Constructor...通過這個示例可以看出,構(gòu)建過程是從父類“向外”擴散的,也就是從父類開始向子類一級一級地完成構(gòu)建。而且我們并沒有顯示的引用父類的構(gòu)造器,這就是java的聰明之處:編譯器會默認給子類調(diào)用父類的構(gòu)造器。
?????? 但是,這個默認調(diào)用父類的構(gòu)造器是有前提的:父類有默認構(gòu)造器。如果父類沒有默認構(gòu)造器,我們就要必須顯示的使用super()來調(diào)用父類構(gòu)造器,否則編譯器會報錯:無法找到符合父類形式的構(gòu)造器。
public class Person {protected String name;protected int age;protected String sex;Person(String name){System.out.println("Person Constrctor-----" + name);} }public class Husband extends Person{private Wife wife;Husband(){super("chenssy");System.out.println("Husband Constructor...");}public static void main(String[] args) {Husband husband = new Husband();} }Output: Person Constrctor-----chenssy Husband Constructor...綜上所述,構(gòu)造函數(shù)有一下幾個特點:
(1)構(gòu)造函數(shù)不能被繼承,只是調(diào)用而已;
(2)子類實例化對象時,如果不顯式調(diào)用父類有參構(gòu)造函數(shù),則會默認先調(diào)用父類的無參構(gòu)造函數(shù)。如果父類沒有無參構(gòu)造函數(shù),且子類的構(gòu)造函數(shù)中又沒有顯式調(diào)用父類的有參構(gòu)造函數(shù),就會出現(xiàn)編譯無法通過。
(3)子類要使用父類的有參構(gòu)造器,必須使用super(參數(shù))形式,且super必須是子類構(gòu)造方法中的第一條語句;
(4)如果沒有任何構(gòu)造函數(shù),系統(tǒng)會默認有一個無參構(gòu)造函數(shù)。創(chuàng)建有參構(gòu)造函數(shù)后,系統(tǒng)將不再有默認無參構(gòu)造函數(shù)了。
3、protected關(guān)鍵字:
private訪問修飾符,對于封裝而言,是最好的選擇,但這個只是基于理想的世界,有時候我們需要這樣的需求:我們需要將某些事物盡可能地對這個世界隱藏,但是仍然允許子類的成員來訪問它們。這個時候就需要使用到protected。
對于protected而言,它指明就類用戶而言,他是private,但是對于任何繼承與此類的子類而言或者其他任何位于同一個包的類而言,他卻是可以訪問的。
public class Person {private String name;private int age;private String sex;protected String getName() {return name;}protected void setName(String name) {this.name = name;}public String toString(){return "this name is " + name;}/** 省略其他setter、getter方法 **/ }public class Husband extends Person{private Wife wife;public String toString(){setName("chenssy"); //調(diào)用父類的setName();return super.toString(); //調(diào)用父類的toString()方法}public static void main(String[] args) {Husband husband = new Husband();System.out.println(husband.toString());} }Output: this name is chenssy從上面示例可以看書子類Husband可以明顯地調(diào)用父類Person的setName()。
?????? 誠然盡管可以使用protected訪問修飾符來限制父類屬性和方法的訪問權(quán)限,但是最好的方式還是將屬性保持為private(我們應當一致保留更改底層實現(xiàn)),通過protected方法來控制類的繼承者的訪問權(quán)限。
4、向上轉(zhuǎn)型:
繼承是is-a的相互關(guān)系,貓繼承與動物,所以我們可以說貓是動物,或者說貓是動物的一種。這樣將貓看做動物就是向上轉(zhuǎn)型。如下:
public class Person {public void display(){System.out.println("Play Person...");}static void display(Person person){person.display();} }public class Husband extends Person{public static void main(String[] args) {Husband husband = new Husband();Person.display(husband); //向上轉(zhuǎn)型} }在這我們通過Person.display(husband)。這句話可以看出husband是person類型。
?????? 將子類轉(zhuǎn)換成父類,在繼承關(guān)系上面是向上移動的,所以一般稱之為向上轉(zhuǎn)型。由于向上轉(zhuǎn)型是從一個叫專用類型向較通用類型的轉(zhuǎn)換,所以它總是安全的,唯一發(fā)生變化的可能就是屬性和方法的丟失。
5、繼承的缺點,謹慎使用繼承:
上面講了繼承所帶來的諸多好處,但是對于繼承,我們也要謹慎使用,因為繼承存在如下缺陷:
(1)繼承是一種強耦合關(guān)系,父類變,子類就必須變。
(2)繼承破壞了封裝,對于父類而言,它的實現(xiàn)細節(jié)對與子類來說都是透明的。
?????? 所以說當我們使用繼承的時候,我們需要確信使用繼承確實是有效可行的辦法。那么到底要不要使用繼承呢?《Think in java》中提供了解決辦法:問一問自己是否需要從子類向父類進行向上轉(zhuǎn)型。如果必須向上轉(zhuǎn)型,則繼承是必要的,但是如果不需要,則應當好好考慮自己是否需要繼承。
三、Java三大特性之:多態(tài)
1、什么是多態(tài):
????????多態(tài)是指同一種行為具有不同的表現(xiàn)形式,也就是說,運行同一段代碼,Java在運行時會根據(jù)調(diào)用對象的不同,而產(chǎn)生不同的結(jié)果。也就是說程序中 定義的引用變量所指向的具體類型 或者?通過該引用變量發(fā)出的方法調(diào)用 在編譯時并不確定,而是在運行期間才確定,即一個引用變量到底會指向哪個類的實例對象,該引用變量發(fā)出的方法調(diào)用到底是哪個類中實現(xiàn)的方法,必須在由程序運行期間才能決定。因為在程序運行時才確定具體的類,這樣,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實現(xiàn)上,從而導致該引用調(diào)用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,這就是多態(tài)性。
2、簡單的多態(tài)例子:
?????? 比如你是一個酒神,對酒情有獨鐘。某日回家發(fā)現(xiàn)桌上有幾個杯子里面都裝了白酒,從外面看我們是不可能知道這是些什么酒,只有喝了之后才能夠猜出來是何種酒。你一喝,這是劍南春、再喝這是五糧液、再喝這是酒鬼酒….在這里我們可以描述成如下:
?????? 酒 a = 劍南春
?????? 酒 b = 五糧液
?????? 酒 c = 酒鬼酒
?????? 這里所表現(xiàn)的的就是多態(tài)。劍南春、五糧液、酒鬼酒都是酒的子類,我們只是通過酒這一個父類就能夠引用不同的子類,這就是多態(tài)——我們只有在運行的時候才會知道引用變量所指向的具體實例對象。
?????? 誠然,要理解多態(tài)我們就必須要明白什么是“向上轉(zhuǎn)型”。在繼承中我們簡單介紹了向上轉(zhuǎn)型,這里就在啰嗦下:在上面的喝酒例子中,酒(Win)是父類,劍南春(JNC)、五糧液(WLY)、酒鬼酒(JGJ)是子類。我們定義如下代碼:
?????? JNC a = new JNC();
?????? 對于這個代碼我們非常容易理解無非就是實例化了一個劍南春的對象嘛!但是這樣呢?
?????? Wine a = new JNC();
?????? 在這里我們這樣理解,這里定義了一個Wine 類型的a,它指向JNC對象實例。由于JNC是繼承與Wine,所以JNC可以自動向上轉(zhuǎn)型為Wine,所以a是可以指向JNC實例對象的。這樣做存在一個非常大的好處,在繼承中我們知道子類是父類的擴展,它可以提供比父類更加強大的功能,如果我們定義了一個指向子類的父類引用類型,那么它除了能夠引用父類的共性外,還可以使用子類強大的功能。
?????? 但是向上轉(zhuǎn)型存在一些缺憾,那就是它必定會導致一些方法和屬性的丟失,而導致我們不能夠獲取它們。所以父類類型的引用可以調(diào)用父類中定義的所有屬性和方法,對于只存在與子類中的方法和屬性它就望塵莫及了。
public class Wine {public void fun1(){System.out.println("Wine 的Fun.....");fun2();}public void fun2(){System.out.println("Wine 的Fun2...");} }public class JNC extends Wine{/*** @desc 子類重寫父類方法* 父類中不存在該方法,向上轉(zhuǎn)型后,父類是不能引用該方法的* @param a* @return void*/public void fun1(String a){System.out.println("JNC 的 Fun1...");fun2();}/*** 子類重寫父類方法* 指向子類的父類引用調(diào)用fun2時,必定是調(diào)用該方法*/public void fun2(){System.out.println("JNC 的Fun2...");} }public class Test {public static void main(String[] args) {Wine a = new JNC();a.fun1();} } ------------------------------------------------- Output: Wine 的Fun..... JNC 的Fun2...????????從程序的運行結(jié)果中我們發(fā)現(xiàn),a.fun1()首先是運行父類Wine中的fun1().然后再運行子類JNC中的fun2()。
????????分析:在這個程序中子類JNC重載了父類Wine的方法fun1(),重寫fun2(),而且重載后的fun1(String a)與 fun1()不是同一個方法,由于父類中沒有該方法,向上轉(zhuǎn)型后會丟失該方法,所以執(zhí)行JNC的Wine類型引用是不能引用fun1(String a)方法。而子類JNC重寫了fun2() ,那么指向JNC的Wine引用會調(diào)用JNC中fun2()方法。
所以對于多態(tài)我們可以總結(jié)如下:
????????指向子類的父類引用由于向上轉(zhuǎn)型了,它只能訪問父類中擁有的方法和屬性,而對于子類中存在而父類中不存在的方法,該引用是不能使用的,盡管是重載該方法。若子類重寫了父類中的某些方法,在調(diào)用該些方法的時候,必定是使用子類中定義的這些方法(動態(tài)連接、動態(tài)調(diào)用)。? ?
????????對于面向?qū)ο蠖?#xff0c;多態(tài)分為編譯時多態(tài)和運行時多態(tài)。方法重載(overload)是編譯時的多態(tài)性,它是根據(jù)參數(shù)列表的不同來區(qū)分不同的函數(shù),通過編譯之后會變成兩個不同的函數(shù),在運行時談不上多態(tài)。方法重寫(overide)是運行時的多態(tài)性,它是通過動態(tài)綁定來實現(xiàn)的,也就是我們所說的多態(tài)性。
3、多態(tài)的實現(xiàn):
????????繼承在為多態(tài)的實現(xiàn)做了準備。子類繼承父類,我們可以編寫一個指向子類的父類類型引用,該引用既可以處理父類對象,也可以處理子類對象,當相同的消息發(fā)送給子類或者父類對象時,該對象就會根據(jù)自己所屬的引用而執(zhí)行不同的行為,這就是多態(tài)。即多態(tài)性就是相同的消息使得不同的類做出不同的響應。
(1)Java實現(xiàn)多態(tài)有兩個必要條件:繼承重寫、向上轉(zhuǎn)型。
繼承:子類繼承父類并重寫父類中的方法
向上轉(zhuǎn)型:在多態(tài)中需要將子類的引用賦給父類對象,只有這樣該引用才能夠具備技能調(diào)用父類的方法和子類的方法,只有父類型引用子類型對象,才能實現(xiàn)同樣的引用調(diào)用同樣的方法,根據(jù)子類對象的不同而表現(xiàn)出不同的行為
????????只有滿足了上述兩個條件,我們才能夠在同一個繼承結(jié)構(gòu)中使用統(tǒng)一的邏輯實現(xiàn)代碼處理不同的對象,從而達到執(zhí)行不同的行為。
????????對于Java而言,它多態(tài)的實現(xiàn)機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型決定了調(diào)用誰的成員方法,但是這個被調(diào)用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。
(2)實現(xiàn)的形式:Java中有兩種形式:繼承與接口。
①基于繼承實現(xiàn)多態(tài):
基于繼承的實現(xiàn)機制主要表現(xiàn)在父類和繼承該父類的一個或多個子類對某些方法的重寫,多個子類對同一方法的重寫可以表現(xiàn)出不同的行為。
public class Wine {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}public Wine(){}public String drink(){return "喝的是 " + getName();}/*** 重寫toString()*/public String toString(){return null;} }public class JNC extends Wine{public JNC(){setName("JNC");}/*** 重寫父類方法,實現(xiàn)多態(tài)*/public String drink(){return "喝的是 " + getName();}/*** 重寫toString()*/public String toString(){return "Wine : " + getName();} }public class JGJ extends Wine{public JGJ(){setName("JGJ");}/*** 重寫父類方法,實現(xiàn)多態(tài)*/public String drink(){return "喝的是 " + getName();}/*** 重寫toString()*/public String toString(){return "Wine : " + getName();} }public class Test {public static void main(String[] args) {//定義父類數(shù)組Wine[] wines = new Wine[2];//定義兩個子類JNC jnc = new JNC();JGJ jgj = new JGJ();//父類引用子類對象wines[0] = jnc;wines[1] = jgj;for(int i = 0 ; i < 2 ; i++){System.out.println(wines[i].toString() + "--" + wines[i].drink());}System.out.println("-------------------------------");} } OUTPUT: Wine : JNC--喝的是 JNC Wine : JGJ--喝的是 JGJ -------------------------------在上面的代碼中JNC、JGJ繼承Wine,并且重寫了drink()、toString()方法,程序運行結(jié)果是調(diào)用子類中方法,輸出JNC、JGJ的名稱,這就是多態(tài)的表現(xiàn)。不同的對象可以執(zhí)行相同的行為,但是他們都需要通過自己的實現(xiàn)方式來執(zhí)行,這就要得益于向上轉(zhuǎn)型了。
??????我們都知道所有的類都繼承自超類Object,toString()方法也是Object中方法,當我們這樣寫時:
Object o = new JGJ(); System.out.println(o.toString());?輸出的結(jié)果是Wine : JGJ。
??????Object、Wine、JGJ三者繼承鏈關(guān)系是:JGJ—>Wine—>Object。所以我們可以這樣說:當子類重寫父類的方法被調(diào)用時,只有對象繼承鏈中的最末端的方法才會被調(diào)用。但是注意如果這樣寫:
Object o = new Wine(); System.out.println(o.toString());輸出的結(jié)果應該是Null,因為JGJ并不存在于該對象繼承鏈中。
??????所以基于繼承實現(xiàn)的多態(tài)可以總結(jié)如下:對于引用子類的父類類型,在處理該引用時,它適用于繼承該父類的所有子類,子類對象的不同,對方法的實現(xiàn)也就不同,執(zhí)行相同動作產(chǎn)生的行為也就不同。
??????如果父類是抽象類,那么子類必須要實現(xiàn)父類中所有的抽象方法,這樣該父類所有的子類一定存在統(tǒng)一的對外接口,但其內(nèi)部的具體實現(xiàn)可以各異。這樣我們就可以使用頂層類提供的統(tǒng)一接口來處理該層次的方法。
②基于接口實現(xiàn)的多態(tài):
繼承是通過重寫父類的同一方法的幾個不同子類來體現(xiàn)的,那么接口就是通過實現(xiàn)接口并覆蓋接口中同一方法的幾不同的類體現(xiàn)的。
??????在接口的多態(tài)中,指向接口的引用必須是指定這實現(xiàn)了該接口的一個類的實例程序,在運行時,根據(jù)對象引用的實際類型來執(zhí)行對應的方法。
??????繼承都是單繼承,只能為一組相關(guān)的類提供一致的服務接口。但是接口可以是多繼承多實現(xiàn),它能夠利用一組相關(guān)或者不相關(guān)的接口進行組合與擴充,能夠?qū)ν馓峁┮恢碌姆战涌凇K运鄬τ诶^承來說有更好的靈活性。
4、經(jīng)典實例:
public class A {public String show(D obj) {return ("A and D");}public String show(A obj) {return ("A and A");} }public class B extends A{public String show(B obj){return ("B and B");}public String show(A obj){return ("B and A");} }public class C extends B{}public class D extends B{}public class Test {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在這里看結(jié)果1、2、3還好理解,從4開始就開始糊涂了,對于4來說為什么輸出不是“B and B”呢?
??????首先我們先看一句話:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調(diào)用誰的成員方法,但是這個被調(diào)用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。這句話對多態(tài)進行了一個概括。其實在繼承鏈中對象方法的調(diào)用存在一個優(yōu)先級:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
分析:
??????從上面的程序中我們可以看出A、B、C、D存在如下關(guān)系。
首先我們分析5,a2.show(c),a2是A類型的引用變量,所以this就代表了A,a2.show(c),它在A類中找發(fā)現(xiàn)沒有找到,于是到A的超類中找(super),由于A沒有超類(Object除外),所以跳到第三級,也就是this.show((super)O),C的超類有B、A,所以(super)O為B、A,this同樣是A,這里在A中找到了show(A obj),同時由于a2是B類的一個引用且B類重寫了show(A obj),因此最終會調(diào)用子類B類的show(A obj)方法,結(jié)果也就是B and A。
??????按照同樣的方法我也可以確認其他的答案。
??????方法已經(jīng)找到了但是我們這里還是存在一點疑問,我們還是來看這句話:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調(diào)用誰的成員方法,但是這個被調(diào)用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。這我們用一個例子來說明這句話所代表的含義:a2.show(b);
??????這里a2是引用變量,為A類型,它引用的是B對象,因此按照上面那句話的意思是說有B來決定調(diào)用誰的方法,所以a2.show(b)應該要調(diào)用B中的show(B obj),產(chǎn)生的結(jié)果應該是“B and B”,但是為什么會與前面的運行結(jié)果產(chǎn)生差異呢?這里我們忽略了后面那句話“但是這兒被調(diào)用的方法必須是在超類中定義過的”,那么show(B obj)在A類中存在嗎?根本就不存在!所以這句話在這里不適用?那么難道是這句話錯誤了?非也!其實這句話還隱含這這句話:它仍然要按照繼承鏈中調(diào)用方法的優(yōu)先級來確認。所以它才會在A類中找到show(A obj),同時由于B重寫了該方法所以才會調(diào)用B類中的方法,否則就會調(diào)用A類中的方法。
? ? ? ?所以多態(tài)機制遵循的原則概括為:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調(diào)用誰的成員方法,但是這個被調(diào)用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法,但是它仍然要根據(jù)繼承鏈中方法調(diào)用的優(yōu)先級來確認方法,該優(yōu)先級為:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
參考文章鏈接:java提高篇(一)-----理解java的三大特性之封裝_chenssy 的技術(shù)博客-CSDN博客
java提高篇(二)-----理解java的三大特性之繼承_chenssy 的技術(shù)博客-CSDN博客
java提高篇(三)-----理解java的三大特性之多態(tài)_chenssy 的技術(shù)博客-CSDN博客_java多態(tài)的特點
總結(jié)
以上是生活随笔為你收集整理的Java基础篇:封装、继承、多态三大特性的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java基础篇:回调机制详解
- 下一篇: Java基础篇:static关键字