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