多態的應用的一大前提是繼承。以及繼承中的重寫方法。繼承的一大前提是封裝,而封裝裏面涉及到重要知識點方法重載。
因此要領會多態,前面的封裝與繼承是基礎。而多態也是前二者的更進一步擴展。就好像負數對正數的擴展同樣,三者又一塊兒共同構建起了面向對象的三大基本特徵。
因此先來大體回憶理解一下這三大特性的來由與特色。
面向對象的思想來自現實生活,符合咱們人類的通常思惟模式,因此三大特性其實也來自咱們的平常生活。也很是的切合平常的生存使用模式。
咱們現實生活中的一切複雜工具其實都是被封裝起來的,在工廠生產線上就已經被封裝了起來。好比電視,咱們只須要通電,打開,選臺,觀看,關閉,斷電就行,核心其實就一個功能——看電視節目。但是這一個功能卻須要幾百種技術,幾十個功能,四五個重要木塊的共同協做才能完成。
而咱們須要知道嗎。不須要的,因而這些功能以及木塊都被外殼封閉了起來。其重要做用就是保護這些設備。而留給咱們的就是一些主要的一選臺爲主的調節按鈕。這些以及裏面的一些鏈接線就是內外溝通的渠道。
在封裝裏面的溝通渠道就是set/get方法。歸根到底安全起到主要做用。
封裝不只僅針對屬性,還有方法。
不只如此,任何工廠每完成一個產品的製造都會有一個簡單的封裝。電視的零部件來自無數的廠家,每一個廠家生產一個零部件基本上都會進行一個簡單的封裝。因此封裝能夠說是層層都有,封裝套封裝,就好像一個集合套一個集合,永遠沒有盡頭同樣。咱們的現實社會也是同樣,家庭封裝爸爸媽媽,而後是小區,街道城市,地區,省市,國家,州域,地球,太陽系等等無窮封裝包涵下去。
這就是封裝的來源。
而參數列表以及構造函數都是爲了內外溝通存在的。
電視一般不只僅要使用他,還想要擁有它,單單的封裝就不足以應付。好比西方國家發明了電視,咱們國家想要也製造出來電視,而不只僅是買回來使用它,那麼最好的辦法就是學會製造電視的技術,引進生產電視的技術,改變生產管理模式。這也是一種繼承,技術上的繼承。即便這個過程複雜而艱難。代價重重,也不能阻礙咱們繼承這種技術的決心。由於這種繼承是人類不斷進步的的最好方式。
就說印刷術,從古代的謄寫,到調版印刷術,到活字印刷術,到油蠟印刷術,到打印機等等,這一個個過程,其實就是一個個繼承的過程。並且不僅僅是繼承,還要在繼承的基礎上作些本身獨有的改進,也正是這些點點滴滴的改進,從量變到質變,促進了技術的跟新換代。
而也正是這一個個繼承,與繼承之上的重寫,存進了咱們文明的而進步。
扯得這麼遠,只是由於面向對象的編程根本上是一種思想,只要咱們從思想上理解了這一設計理念,才能更好的使用它,因此這裏扯了不少概念性的動心。
轉回多態。
正由於一個個的繼承,特別是一個個的重寫,致使子類的多種多樣,致使了不一樣與父類的多種樣式,百花齊放,才裝點了多姿多態的社會。多態你也能夠理解爲個性。而不是流水線產品,也不是樣板戲這些雖然經典可是無趣的社會。
這就是多態的含義,也是多態來源於封裝與繼承的基礎的緣由。
多態主要是方法的多態,以及返回類型的多態。
實際上仍然是數據類型的多態。來回處理數據的主要部分在於兩個地方,一個是方法的參數列表另外一個是返回值的位置。由於身處位置不一樣而出現了二者的區分,兩種主要的多態應用。這二者均可以算是不一樣數據類型的多態。
因此只要理解了其中之一基本上就明白了另外一個。
而多態的重點難點就是數據類型的不斷上下轉型,因此下面主要解釋的就是向上轉型,向下轉型。編程
向上轉型,向下轉型,平型轉型。
public class Docter {
public void cure(Pet pet){
if (pet.getHealth()<60) {
pet.toHostipal();
pet.setHealth(60);
}
看病的方法裏面的而參數列表,有Pet類型,就能夠調用Pet裏面的屬性。不用new。很神奇。並且由於Pet是父類,因此能夠傳遞全部子類的參數。調用子類的方法。更加神奇。是否是。安全
前面咱們說參數列表有說道,爲了處理各類類型的數據,能夠設置多種參數列表的重載方法,造成一整套數據處理的方案。爲的就是可以處理足夠多類型的數據。但是此處只須要用父類類型一個參數就能傳遞全部的子類類型的數據。是否是更加神奇。這就是多態的一大用處。函數
這就是將父類做爲形參,形式上的參數。工具
編譯時類型與運行時類型:測試
可是若是咱們將Pet類型下降,降爲Dog類型。
public void cure(Dog pet){…………}
那麼只能傳遞Dog類型的參數,調用它的方法,其餘的動物子類好比Bird的參數就不能傳遞,只能強制裝行爲Dog類型的才能傳遞。可是編譯不報錯,運行時卻會報錯。
這也算是平行轉型了。到了這裏就大概明白爲什麼要向上轉型了。由於此處的方法限制傳遞的參數爲Pet類型,因此首先要將全部的參數轉化爲Pet類型才能傳遞。
Pet pet=new Dog();
這個很像數據類型轉換。或者根本就是一個原理,畢竟都是在進行數據處理。以下;
double a=11*2.5;
這裏就是講new的Dog對象轉型爲Pet類型。才能傳參。可是實質仍是Dog類型的數據。因此運行的時候調用的其實仍是Dog的屬性與方法。這裏的Pet類型只是爲了一步到位能夠傳遞全部的子類參數與方法。
舉一個不恰當的例子,公路的級別越高,能運行的車輛類型與數量越高,可是實質上行駛的車輛是自行車仍是自行車,是汽車仍是汽車。不會由於公路等級的增長而變化。
這就是編譯時類型與運行時類型。
編譯時類型有聲明該變量的使用類型決定,通常都是父類的高等級類型。
運行時類型有實際付給該變量的對象決定。通常都是子類的低等級類型。
編譯類型就好像形式參數,運勢型類型就好像實際參數。spa
這個時候同一個聲明的類型,就能經過調用不一樣子類的參數與方法,實現子類的重寫方法。
這樣一解釋咱們是否是發現這一理解跟多態的定義很類似啊。設計
多態:同一個引用類型(聲明類型),使用不一樣的實例(實質參數)執行不一樣的操做(實質的方法。)
自動類型轉換與強制類型裝換:
不會由於等級的增高而變化,卻會由於等級的下降而變化,好比高速公路,堵塞,爲了趕路只能走一截鄉間馬路繞路而走。那麼跑車就不能行駛了,除非事先對其底盤等等車體進行改裝,否則磕磕碰碰,回廠返修成爲了必然。
這樣一來,低級車輛行駛在高級公路上只會更舒服,不會有損耗,可是高等級的車輛行駛在低等級的道路上必然會有損耗,並且還必須進行相應的改裝。嘿嘿,越是低級的工具越能適應高級的道路,凡是是高級的工具反而很難使用於低級的公路。這樣一來咱們的自行車11路反而是最實用的交通工具了。嘿嘿。
這就是自動類型轉換無損耗與強制類型裝換有損耗的緣由吧。以下:
double a=112.5;//=27.7
int b=(int)(112.5);=27
自動類型轉換沒有數據損耗,可是強制類型轉化就有損耗。損耗了0.5.
很差意思舉得例子不恰當。code
向上轉型和向下轉型:
咱們能夠調用父類子類都有的屬性與方法。可是卻不能調用子類所獨有的方法。好比Dog類中的run方法。以下:
public void run() {
System.out.println("小狗能跑");
}
由於已經通過向上轉型變爲了父類的類型,名義上好似是在用父類的對象調用一切,能夠調用與父類有關係的子類的方法,可是子類本身的獨有方法確實屬於子類獨有,與父類沒有關係,中間差了一個鏈接。
就好似子類的私人物品沒有也不必在父類哪裏登記造冊。或者說是原本就不能調用私有的方法。由於父子類重寫的方法不能下降訪問權限,因此能夠說仍然是公有的方法。這纔是可以調用的另外一個緣由。
這個時候能夠從新用咱們的萬金油犯法調用。
Dog dog=new Dog();
代碼變爲以下:
public void cure(Pet pet){
if (pet.getHealth()<60) {
pet.toHostipal();
pet.setHealth(60);
Dog dog=new Dog();
dog.run();
}
確實可以調用。
而實際上咱們都知道此時參數列表傳遞進來的pet本質上就是一個Dog類型的參數。前面已經new過了,這裏應該沒有在new的重複必要了吧。那麼咱們將其兩相結合。進行一下精簡:
Dog dog=(Dog)pet;
對其進行強制轉換。其實質不過是轉回來而已。假如你覺得換了一套馬甲就能夠換成另外一我的,向下轉型強制轉換爲小鳥類
Bird bird=(Bird )pet。
那你就錯了。即便編譯器不報錯。可是運行時就會報錯,轉換類型的錯誤。進錯房間了。如上面平行轉型報錯的結果同樣。你一個披着小鳥外衣的小狗還能調用小鳥的私有財產不成。只能以下:
public void cure(Pet pet){
if (pet.getHealth()<60) {
pet.toHostipal();
pet.setHealth(60);
Dog dog=(Dog)pet;
dog.run();
}
嘿嘿,效果同樣,這就是向下轉型。即便你把dog改成bird。
Dog bird=(Dog)pet;
效果仍是同樣。這樣的結果在測試的時候new什麼類型的對象的時候就決定了。
其實不過是爲了工做須要出外辦公換了一套馬甲,回家了天然要換回來的同樣。就好像明星爲了工做化濃妝(向上轉型),工做完了,卸妝回家(向下轉型)。能用的仍是隻有本身的東西。沒可能範爺的替身出神入化的打扮成一個範爺的妝容就可使用李晨的東西了。
向上轉型,向下轉型,轉了一圈又轉了回來。完整的代碼以下:
Pet pet=new Dog();//子類到父類的向上轉型。
pet.toHospital();//Pet類型的變量調用Dog類重寫方法。對象
Dog dog=(Dog)pet;//父類裝換爲子類的強制類型轉換,向下轉型。
Bird bird=(Bird )pet;//這個就是類型轉換報錯。繼承
不過若是範爺的替身跟範爺真的好想一個模子出來的話,李晨未必認得,無論真認不出來仍是不肯意認出來。嘿咻嘿咻。
這時候就有問題了。因此必須有一道檢驗的手續,檢驗真假明星,要否則我也去整一個範爺的容貌,出席一個活動,掙個幾百萬,在整回來。雖然我是男的。稍微困難了一些。嘿嘿。
因此檢驗的手續就成爲了必不可少的。這就是instanceof運算符。
修建這個檢驗手段的方法也就是語法就是:
<引用變量> instanceof<類接口/接口名稱>
好比:
if(pet instanceof Dog ){……}else if(pet instanceof Bird){……}
方法體就不寫了。
固然這個判斷必須在使用以前進行判斷,否則都滾牀單了,還判斷個屁啊。
其實咱們一直在醫生類裏面打轉,進行修改。而通常這樣的類算是封裝起來了。通常不對其進行修改。原來的代碼以下:
public void cure(Pet pet){
if (pet.getHealth()<60) { pet.toHostipal(); pet.setHealth(60);}}
已經完成了本身的看病方法,能夠封裝起來。
至於到底傳了寫什麼參數,類型怎麼轉換,徹底應該放到外面進行,好比測試類裏面。
這樣的好處是能夠不用管向上向下轉型了,就是向上轉型也容易理解一些。固然前面那樣作不過是爲了講解清楚而已。
知道了原理,咱們仍是用簡單清晰的辦法來解決問題的好。
就好比咱們在測試類裏面這樣寫:
Dog dog=new Dog();
bird bird=new bird();
Docter doctoer=new Doctoer();
docter.cure(bird);
docter.cure(dog);
將中間的轉型過程徹底交給系統,讓他本身默默地完成就行。咱們就不用參與這麼基層的工做了。這也算封裝的好處。將方法封裝起來,咱們只須要拿來用就行,簡單省事多了。
從這裏更能見識到父類做爲參數類型處理數據的偉大。和前面的一整套重載方法配合起來,簡直能處理絕大多數數據。包含基本類型數據,引用類型數據,自定義類型數據。是否是很酷。
若是父類中沒有這個方法,則說明這是子類獨有的方法,則不能調用。若是父類有而子類沒有,則調用的是父類的方法。在這裏,好像也蘊含了方法的重寫,不過不是重寫,而是從新輸出。子類的方法覆蓋了父類的方法。這個還真沒有找到測試的辦法來驗證是否如此。