java 封装 继承和多态
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
多態(tài)無處不在。引用的概念最為困難。理解了引用才能理解多態(tài)的意義以及應(yīng)用。
多態(tài)的應(yīng)用的一大前提是繼承。以及繼承中的重寫方法。繼承的一大前提是封裝,而封裝里面涉及到重要知識點(diǎn)方法重載。
所以要領(lǐng)會多態(tài),前面的封裝與繼承是基礎(chǔ)。而多態(tài)也是前兩者的更進(jìn)一步擴(kuò)展。就好像負(fù)數(shù)對正數(shù)的擴(kuò)展一樣,三者又一起共同構(gòu)建起了面向?qū)ο蟮娜蠡咎卣鳌?br /> 所以先來大致回憶理解一下這三大特性的來由與特點(diǎn)。
面向?qū)ο蟮乃枷雭碜袁F(xiàn)實(shí)生活,符合我們?nèi)祟惖囊话闼季S模式,所以三大特性其實(shí)也來自我們的日常生活。也非常的切合日常的生存使用模式。
我們現(xiàn)實(shí)生活中的一切復(fù)雜工具其實(shí)都是被封裝起來的,在工廠生產(chǎn)線上就已經(jīng)被封裝了起來。比如電視,我們只需要通電,打開,選臺,觀看,關(guān)閉,斷電就行,核心其實(shí)就一個功能——看電視節(jié)目。可是這一個功能卻需要幾百種技術(shù),幾十個功能,四五個重要木塊的共同協(xié)作才能完成。
而我們需要知道嗎。不需要的,于是這些功能以及木塊都被外殼封閉了起來。其重要作用就是保護(hù)這些設(shè)備。而留給我們的就是一些主要的一選臺為主的調(diào)節(jié)按鈕。這些以及里面的一些連接線就是內(nèi)外溝通的渠道。
在封裝里面的溝通渠道就是set/get方法。歸根到底安全起到主要作用。
封裝不僅僅針對屬性,還有方法。
不僅如此,任何工廠每完成一個產(chǎn)品的制造都會有一個簡單的封裝。電視的零部件來自無數(shù)的廠家,每個廠家生產(chǎn)一個零部件基本上都會進(jìn)行一個簡單的封裝。所以封裝可以說是層層都有,封裝套封裝,就好像一個集合套一個集合,永遠(yuǎn)沒有盡頭一樣。我們的現(xiàn)實(shí)社會也是一樣,家庭封裝爸爸媽媽,然后是小區(qū),街道城市,地區(qū),省市,國家,州域,地球,太陽系等等無窮封裝包涵下去。
這就是封裝的來源。
而參數(shù)列表以及構(gòu)造函數(shù)都是為了內(nèi)外溝通存在的。
電視通常不僅僅要使用他,還想要擁有它,單單的封裝就不足以應(yīng)付。比如西方國家發(fā)明了電視,我們國家想要也制造出來電視,而不僅僅是買回來使用它,那么最好的辦法就是學(xué)會制造電視的技術(shù),引進(jìn)生產(chǎn)電視的技術(shù),改變生產(chǎn)管理模式。這也是一種繼承,技術(shù)上的繼承。即使這個過程復(fù)雜而艱難。代價重重,也不能阻礙我們繼承這種技術(shù)的決心。因?yàn)檫@種繼承是人類不斷進(jìn)步的的最好方式。
就說印刷術(shù),從古代的謄寫,到調(diào)版印刷術(shù),到活字印刷術(shù),到油蠟印刷術(shù),到打印機(jī)等等,這一個個過程,其實(shí)就是一個個繼承的過程。而且不單單是繼承,還要在繼承的基礎(chǔ)上做些自己獨(dú)有的改進(jìn),也正是這些點(diǎn)點(diǎn)滴滴的改進(jìn),從量變到質(zhì)變,促進(jìn)了技術(shù)的跟新?lián)Q代。
而也正是這一個個繼承,與繼承之上的重寫,存進(jìn)了我們文明的而進(jìn)步。
扯得這么遠(yuǎn),只是因?yàn)槊嫦驅(qū)ο蟮木幊谈旧鲜且环N思想,只要我們從思想上理解了這一設(shè)計(jì)理念,才能更好的使用它,所以這里扯了很多概念性的動心。
轉(zhuǎn)回多態(tài)。
正因?yàn)橐粋€個的繼承,特別是一個個的重寫,導(dǎo)致子類的多種多樣,導(dǎo)致了不同與父類的多種樣式,百花齊放,才裝點(diǎn)了多姿多態(tài)的社會。多態(tài)你也可以理解為個性。而不是流水線產(chǎn)品,也不是樣板戲這些雖然經(jīng)典但是無趣的社會。
這就是多態(tài)的含義,也是多態(tài)來源于封裝與繼承的基礎(chǔ)的原因。
多態(tài)主要是方法的多態(tài),以及返回類型的多態(tài)。
實(shí)際上仍然是數(shù)據(jù)類型的多態(tài)。來回處理數(shù)據(jù)的主要部分在于兩個地方,一個是方法的參數(shù)列表另一個是返回值的位置。因?yàn)樯硖幬恢貌煌霈F(xiàn)了兩者的區(qū)分,兩種主要的多態(tài)應(yīng)用。這兩者都可以算是不同數(shù)據(jù)類型的多態(tài)。
所以只要理解了其中之一基本上就明白了另一個。
而多態(tài)的重點(diǎn)難點(diǎn)就是數(shù)據(jù)類型的不斷上下轉(zhuǎn)型,所以下面主要解釋的就是向上轉(zhuǎn)型,向下轉(zhuǎn)型。
向上轉(zhuǎn)型,向下轉(zhuǎn)型,平型轉(zhuǎn)型。
public class Docter {
public void cure(Pet pet){
if (pet.getHealth()<60) {
pet.toHostipal();
pet.setHealth(60);
}
看病的方法里面的而參數(shù)列表,有Pet類型,就可以調(diào)用Pet里面的屬性。不用new。很神奇。而且因?yàn)镻et是父類,所以可以傳遞所有子類的參數(shù)。調(diào)用子類的方法。更加神奇。是不是。
前面我們說參數(shù)列表有說道,為了處理各種類型的數(shù)據(jù),可以設(shè)置多種參數(shù)列表的重載方法,形成一整套數(shù)據(jù)處理的方案。為的就是能夠處理足夠多類型的數(shù)據(jù)。可是此處只需要用父類類型一個參數(shù)就能傳遞所有的子類類型的數(shù)據(jù)。是不是更加神奇。這就是多態(tài)的一大用處。
這就是將父類作為形參,形式上的參數(shù)。
編譯時類型與運(yùn)行時類型:
但是如果我們將Pet類型降低,降為Dog類型。
public void cure(Dog pet){…………}
那么只能傳遞Dog類型的參數(shù),調(diào)用它的方法,其他的動物子類比如Bird的參數(shù)就不能傳遞,只能強(qiáng)制裝行為Dog類型的才能傳遞。但是編譯不報(bào)錯,運(yùn)行時卻會報(bào)錯。
這也算是平行轉(zhuǎn)型了。到了這里就大概明白為何要向上轉(zhuǎn)型了。因?yàn)榇颂幍姆椒ㄏ拗苽鬟f的參數(shù)為Pet類型,所以首先要將所有的參數(shù)轉(zhuǎn)化為Pet類型才能傳遞。
Pet pet=new Dog();
這個很像數(shù)據(jù)類型轉(zhuǎn)換。或者根本就是一個原理,畢竟都是在進(jìn)行數(shù)據(jù)處理。如下;
double a=11*2.5;
這里就是講new的Dog對象轉(zhuǎn)型為Pet類型。才能傳參。但是實(shí)質(zhì)還是Dog類型的數(shù)據(jù)。所以運(yùn)行的時候調(diào)用的其實(shí)還是Dog的屬性與方法。這里的Pet類型只是為了一步到位可以傳遞所有的子類參數(shù)與方法。
舉一個不恰當(dāng)?shù)睦?#xff0c;公路的級別越高,能運(yùn)行的車輛類型與數(shù)量越高,但是實(shí)質(zhì)上行駛的車輛是自行車還是自行車,是汽車還是汽車。不會因?yàn)楣返燃壍脑黾佣兓?br /> 這就是編譯時類型與運(yùn)行時類型。
編譯時類型有聲明該變量的使用類型決定,一般都是父類的高等級類型。
運(yùn)行時類型有實(shí)際付給該變量的對象決定。一般都是子類的低等級類型。
編譯類型就好像形式參數(shù),運(yùn)勢型類型就好像實(shí)際參數(shù)。
這個時候同一個聲明的類型,就能通過調(diào)用不同子類的參數(shù)與方法,實(shí)現(xiàn)子類的重寫方法。
這樣一解釋我們是不是發(fā)現(xiàn)這一理解跟多態(tài)的定義很相似啊。
多態(tài):同一個引用類型(聲明類型),使用不同的實(shí)例(實(shí)質(zhì)參數(shù))執(zhí)行不同的操作(實(shí)質(zhì)的方法。)
自動類型轉(zhuǎn)換與強(qiáng)制類型裝換:
不會因?yàn)榈燃壍脑龈叨兓?#xff0c;卻會因?yàn)榈燃壍慕档投兓?#xff0c;比如高速公路,堵塞,為了趕路只能走一截鄉(xiāng)間馬路繞路而走。那么跑車就不能行駛了,除非事先對其底盤等等車體進(jìn)行改裝,不然磕磕碰碰,回廠返修成為了必然。
這樣一來,低級車輛行駛在高級公路上只會更舒服,不會有損耗,但是高等級的車輛行駛在低等級的道路上必然會有損耗,而且還必須進(jìn)行相應(yīng)的改裝。嘿嘿,越是低級的工具越能適應(yīng)高級的道路,凡是是高級的工具反而很難使用于低級的公路。這樣一來我們的自行車11路反而是最實(shí)用的交通工具了。嘿嘿。
這就是自動類型轉(zhuǎn)換無損耗與強(qiáng)制類型裝換有損耗的原因吧。如下:
double a=112.5;//=27.7
int b=(int)(112.5);=27
自動類型轉(zhuǎn)換沒有數(shù)據(jù)損耗,但是強(qiáng)制類型轉(zhuǎn)化就有損耗。損耗了0.5.
不好意思舉得例子不恰當(dāng)。
向上轉(zhuǎn)型和向下轉(zhuǎn)型:
我們可以調(diào)用父類子類都有的屬性與方法。但是卻不能調(diào)用子類所獨(dú)有的方法。比如Dog類中的run方法。如下:
public void run() {
System.out.println("小狗能跑");
}
因?yàn)橐呀?jīng)經(jīng)過向上轉(zhuǎn)型變?yōu)榱烁割惖念愋?#xff0c;名義上好似是在用父類的對象調(diào)用一切,可以調(diào)用與父類有關(guān)系的子類的方法,但是子類自己的獨(dú)有方法確實(shí)屬于子類獨(dú)有,與父類沒有關(guān)系,中間差了一個連接。
就好似子類的私人物品沒有也沒必要在父類哪里登記造冊。或者說是本來就不能調(diào)用私有的方法。因?yàn)楦缸宇愔貙懙姆椒ú荒芙档驮L問權(quán)限,所以可以說仍然是公有的方法。這才是能夠調(diào)用的另一個原因。
這個時候可以重新用我們的萬金油犯法調(diào)用。
Dog dog=new Dog();
代碼變?yōu)槿缦?#xff1a;
public void cure(Pet pet){
if (pet.getHealth()<60) {
pet.toHostipal();
pet.setHealth(60);
Dog dog=new Dog();
dog.run();
}
確實(shí)能夠調(diào)用。
而實(shí)際上我們都知道此時參數(shù)列表傳遞進(jìn)來的pet本質(zhì)上就是一個Dog類型的參數(shù)。前面已經(jīng)new過了,這里應(yīng)該沒有在new的重復(fù)必要了吧。那么我們將其兩相結(jié)合。進(jìn)行一下精簡:
Dog dog=(Dog)pet;
對其進(jìn)行強(qiáng)制轉(zhuǎn)換。其實(shí)質(zhì)不過是轉(zhuǎn)回來而已。假如你以為換了一套馬甲就可以換成另一個人,向下轉(zhuǎn)型強(qiáng)制轉(zhuǎn)換為小鳥類
Bird bird=(Bird )pet。
那你就錯了。即使編譯器不報(bào)錯。但是運(yùn)行時就會報(bào)錯,轉(zhuǎn)換類型的錯誤。進(jìn)錯房間了。如上面平行轉(zhuǎn)型報(bào)錯的結(jié)果一樣。你一個披著小鳥外衣的小狗還能調(diào)用小鳥的私有財(cái)產(chǎn)不成。只能如下:
public void cure(Pet pet){
if (pet.getHealth()<60) {
pet.toHostipal();
pet.setHealth(60);
Dog dog=(Dog)pet;
dog.run();
}
嘿嘿,效果一樣,這就是向下轉(zhuǎn)型。即使你把dog改為bird。
Dog bird=(Dog)pet;
效果還是一樣。這樣的結(jié)果在測試的時候new什么類型的對象的時候就決定了。
其實(shí)不過是為了工作需要出外辦公換了一套馬甲,回家了自然要換回來的一樣。就好像明星為了工作化濃妝(向上轉(zhuǎn)型),工作完了,卸妝回家(向下轉(zhuǎn)型)。能用的還是只有自己的東西。沒可能范爺?shù)奶嫔沓錾袢牖拇虬绯梢粋€范爺?shù)膴y容就可以使用李晨的東西了。
向上轉(zhuǎn)型,向下轉(zhuǎn)型,轉(zhuǎn)了一圈又轉(zhuǎn)了回來。完整的代碼如下:
Pet pet=new Dog();//子類到父類的向上轉(zhuǎn)型。
pet.toHospital();//Pet類型的變量調(diào)用Dog類重寫方法。
Dog dog=(Dog)pet;//父類裝換為子類的強(qiáng)制類型轉(zhuǎn)換,向下轉(zhuǎn)型。
Bird bird=(Bird )pet;//這個就是類型轉(zhuǎn)換報(bào)錯。
不過如果范爺?shù)奶嫔砀稜斦娴暮孟胍粋€模子出來的話,李晨未必認(rèn)得,不管真認(rèn)不出來還是不愿意認(rèn)出來。嘿咻嘿咻。
這時候就有問題了。所以必須有一道檢驗(yàn)的手續(xù),檢驗(yàn)真假明星,要不然我也去整一個范爺?shù)娜菝?#xff0c;出席一個活動,掙個幾百萬,在整回來。雖然我是男的。稍微困難了一些。嘿嘿。
所以檢驗(yàn)的手續(xù)就成為了必不可少的。這就是instanceof運(yùn)算符。
修建這個檢驗(yàn)手段的方法也就是語法就是:
<引用變量> instanceof<類接口/接口名稱>
比如:
if(pet instanceof Dog ){……}else if(pet instanceof Bird){……}
方法體就不寫了。
當(dāng)然這個判斷必須在使用之前進(jìn)行判斷,不然都滾床單了,還判斷個屁啊。
其實(shí)我們一直在醫(yī)生類里面打轉(zhuǎn),進(jìn)行修改。而一般這樣的類算是封裝起來了。一般不對其進(jìn)行修改。原來的代碼如下:
public void cure(Pet pet){
已經(jīng)完成了自己的看病方法,可以封裝起來。
至于到底傳了寫什么參數(shù),類型怎么轉(zhuǎn)換,完全應(yīng)該放到外面進(jìn)行,比如測試類里面。
這樣的好處是可以不用管向上向下轉(zhuǎn)型了,就是向上轉(zhuǎn)型也容易理解一些。當(dāng)然前面那樣做不過是為了講解清楚而已。
知道了原理,我們還是用簡單清晰的辦法來解決問題的好。
就比如我們在測試類里面這樣寫:
Dog dog=new Dog();
bird bird=new bird();
Docter doctoer=new Doctoer();
docter.cure(bird);
docter.cure(dog);
將中間的轉(zhuǎn)型過程完全交給系統(tǒng),讓他自己默默地完成就行。我們就不用參與這么基層的工作了。這也算封裝的好處。將方法封裝起來,我們只需要拿來用就行,簡單省事多了。
從這里更能見識到父類作為參數(shù)類型處理數(shù)據(jù)的偉大。和前面的一整套重載方法配合起來,簡直能處理絕大多數(shù)數(shù)據(jù)。包含基本類型數(shù)據(jù),引用類型數(shù)據(jù),自定義類型數(shù)據(jù)。是不是很酷。
如果父類中沒有這個方法,則說明這是子類獨(dú)有的方法,則不能調(diào)用。如果父類有而子類沒有,則調(diào)用的是父類的方法。在這里,好像也蘊(yùn)含了方法的重寫,不過不是重寫,而是重新輸出。子類的方法覆蓋了父類的方法。這個還真沒有找到測試的辦法來驗(yàn)證是否如此。
轉(zhuǎn)載于:https://my.oschina.net/weiweiblog/blog/690358
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的java 封装 继承和多态的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于.NET平台常用的框架整理(转)
- 下一篇: static在类中的定义,和enum的用