java多态总结
?
/*
一、多態:多態是指一個事物的多種存在狀態(一個事物的多種體現形態或者一個事物的多種表現形態)
1、多態的體現:
多態在代碼中的體現為:父類引用指向了子類對象
即 父類 a = new 子類();
2、多態的前提:
1,類與類之間必須存在關系,要么是繼承,要么是實現(類實現接口,接口相當于父類,該類相當于子類)
2,子類要覆蓋父類的方法,即要存在覆蓋(覆寫)。這樣就會使得,用父類引用調用父類和子類共有的
方法(即子類所覆蓋的方法)時,會執行(調用)子類定義里的方法(覆蓋父類方法的子類方法)。為什么會
出現這樣的現象呢?
原因是,該事物的本質是子類對象,只不過這一時刻,其扮演的父類的對象的角色。它盡可能演好父類,但
他畢竟是子類,你可以拿著父類的引用,將其當成父類使用,但是,你所訪問的都是子類對象的內容。包括
父類中的一些方法,全部都是子類的方法。
3、多態的好處:
1,極大的提高了程序的擴展性,使得在升級程序時,變得容易
4、多態的弊端:
1,使用多態時,即用父類引用指向子類對象時,不可以用父類引用訪問子類中特有的方法。只能訪問父類中已有的
方法。
5、轉型:
向上轉型:引用變量的向上轉型是自動完成的,在子類對象賦給父類引用時,會將子類對象的類型轉換成父類類型。
例如:Fu f = new Zi();
數值類型的變量的向上轉型也是自動完成的,例如 : byte b = 3 ; int a = b ;
向下轉型:引用變量的向下轉型是強制完成的,指向子類對象的父類引用可以通過強制類型轉換,由父類類型轉換成
子類類型。
例如 Zi z = (Zi)f; // Fu f = new Zi();
數值類型的變量的向下轉型也是強制完成的,例如: int a = 3 ; byte b = (byte)a;
6、編譯器對于轉型的檢查:
6.1、 對于引用類型的向下轉型,編譯器無法判斷對錯,只能在運行時判斷正確與否。因為
一個父類可能有多個子類,父類引用F可能指向子類A的對象,也可能指向子類B的對象,只要
指向子類對象,這一句就沒有錯。
F f = new A() ; //編譯通過,因為A是F的子類
而一個父類引用被強制轉換成子類類型A,或者子類類型B,都是正確的。所以這一句強制轉換
也沒有錯。
F f = new A();
B b = (B)f ; //編譯通過,因為f是父類F類型的引用,B是F的子類。但是運行時就會
//報錯,因為f本質上是子類A的對象,其無法轉成子類B的對象,它扮演
//不了子類B的對象,因為其不具備子類B中的某些方法,它能扮演父類F
//是因為其具備所有父類的功能,能成功扮演父類F的對象。
6.2 、 但對于引用類型的向上轉型,編譯器是可以檢查出正確與否的。標準就是,子類類型轉父類
類型沒問題。
7、多態的本質是:一個類的對象在多種存在狀態間切換。自始至終不變化的是這個對象,變化的是該對象的對外提供的
訪問類型,或者說變化的是該對象對外提供的存在狀態。
7.1、一個生動的例子:
一個父親:老王,一個兒子:小王。“老王”,“小王”都是名字。但是這個名字
表示出了哪個是父親,哪個是兒子,父親是一種類型,代表的是老一代,兒子是另一種類型,代表
的是新一代。即
兒子繼承父親。
這時候,認識父親的老李來了,老李老找父親老王去講課,講工商管理,父親不在,兒子小王
在家,這時,兒子小王化妝成父親老王(將子類對象賦值給父類引用),開門見到老李,
老李拉著老王(小王化妝的)就走了,到了教室里,老李讓老王開始講課吧(老李調用老王的
講課方法,即“老王.講課()”),老王(兒子小王)上了講臺就開始講課,一開口講的是
“Java編程”,為什么呢?因為老王是小王扮演的,小王不會講工商管理,小王只會講Java編程,
小王講完后回家。
這時候,小李來找小王,小王還沒有卸妝,開門后,小李說“王叔叔,我找小王看電影”,老王
(小王扮演的)說“等會啊,我去叫他”,老王到了屋里就開始卸妝(將指向自己的子類對象的
父類引用強制轉換成子類類型,賦給子類引用,即 "兒子 小王 = (兒子)老王; "),然后出來
和小李一起去看電影了(小李調用了小王的看電影方法,"小王.看電影()")。
為什么老王不能去看電影,直接讓小李調用老王.看電影不行么?不行!因為老王(父類)沒有
這個方法,這是子類所特有的,調用就會出錯,找不到該方法。
分別對應這樣的兩個方法:
public void static main(String[] args) {父親 老王 = new 兒子();//只有兒子在家,兒子扮演老王老李找老王講課(老王);兒子 小王 = (兒子)老王; //卸妝,兒子小王變回兒子狀態,做回自己小李找小王看電影(小王) } public static void 老李找老王講課(父親 講課人) {講課人.講課(); } public static void 小李找小王看電影(兒子 看電影的人) {看電影的人.看電影(); } 8、總結:
1、對于對象而言,即對應通過對象可以訪問的類成員。
任何一個名稱(標識符)都必須有一個類型,類型就確定了該名稱所能對外提供的功能。名稱
是外界使用該對象的一個稱謂(句柄,標志),通過該名稱(其代表一個引用變量的值即地址)
可以拿到該對象,通過該對象可以使用該對象所具有的功能,而該對象通過該名稱對外提供的
功能由該名稱的類型所確定。
類型 名稱 對象
類 引用變量名 new 類();
?
2、對于類名而言,即對應只需要類名即可訪問的類成員。
名稱就是類名,既是對外提供的稱謂(句柄、標志),也表征了類型,從而也就確定其對外所
能提供的功能。通過該名稱就可以使用這種情況下該類型所提供的功能。類名的值就是該類在
方法區中的首地址。
類型(名稱)
類名
3、對于非引用類型的變量而言,即對應于數值類型的變量
名稱必須有一個類型,類型確定了該變量對外所能提供的功能,名稱是該變量的稱謂(代表)
,通過該名稱就可以使用變量,從而使用變量所提供的功能(一般就是存儲功能)。
類型 名稱 值
類型 變量名 值
4、而上述三種情況的名稱分別對應如下:
三種情況的名稱本質上都是對其內的值的引用。出現名稱的地方就相當于將其名稱所
所代表的變量的值寫在那。在代碼中名稱和值之間是一一映射的。
所以,看到變量名稱就將其對應成其所代表的值(內存空間的數據)或者內存空間。
變量的名稱在代碼執行時,相當于變量的值。這一過程是這樣做到的:
首先在編譯時,編譯器會將變量名稱編譯成一個邏輯地址,該邏輯地址是該變量的邏輯地址的
首地址,在執行代碼,分配內存的時候才會在棧內存(或方法區)中分配物理地址,當使用該變量
名時,就會先通過邏輯地址和物理地址之間的映射(即地址的虛實變換),找到該變量的物理地址
的首地址,然后通過該變量的類型(即名稱前的類型),確定該內存地址空間的長度(大小),
從而正確的取出該內存空間中的二進制數據,并根據變量的類型正確解析出來值。至此,就完成了
變量名到值轉換過程,該值就是變量的值,就是該名稱所代表的變量的值。然后拿著該值進行操作
,完成語句的執行。
例1: int a = 3;
int b = a*4;
現在內存中開分配空間,存儲數字3的二進制,執行第二句時,看到名稱(標志符)a會將
變量a的值3取出來,進行和4的乘法操作,然后再賦值到b中。
第二句對應的匯編語句至少有4條:
將a內存空間中的值取出來放入cpu寄存器A,
將4從內存中取出來放入cpu寄存器B,
將寄存器A和B進行乘法運算,并將結果放到A中
再將A中的數據存放到b所代表的內存空間中。
例2: Animal a = new Animal();
a.eat();
a.age = 3;
先在棧內存中分配引用變量a,堆內存中分配對象內存。第二句,看到名稱名稱a會將引用
變量a中的值,即對象在堆內存中的首地址,取出來(取出過程如上面分析)。然后用該
該值進行“.”運算,找到堆內存中的該對象,然后訪問的是eat()成員,所以查找該對象
的成員方法表,在方法區中找到該方法的代碼,執行該函數(在棧內存中開辟該方法的
空間,用來存儲該方法內的局部變量)。
第三句執行時,看到名稱a取出其值,即對象的首地址,通過.運算,找到該對象,然后
訪問的是age成員,通過成員age的類型,結合對象的首地址,計算出age在堆內存中的
首地址,然后找到該空間,將3存入該空間。即時是a.age中的age也是代表其所代表的空間
中的值,其值原來為0,現在被賦值3。
例3: Animal.sleep();
先加載類Animal進入內存中的方法區,然后通過Animal取出Animal的值,即其所代表的
類Animal的內存首地址。進行“.”運算找到類Animal,訪問成員方法sleep()。
這里講的值本質上是一段內存空間中的數據(取值時)或一段內存空間(賦值時)。
9、instanceof 關鍵字,是一個二元操作符,使用方法:
引用變量名稱 instanceof 類名
用來判斷引用變量名稱所代表的引用變量指向的對象是否是一個“類名”所表示的類的實例
即用來判斷引用變量所指向的對象(實例)是不是(可以當成、看成、轉成)某一個類的對象(實例)。
若是,則返回true,否則返回false。
例如:
Animal a = new Cat();//向上轉型,自動進行。類型提升 if(a instanceof Cat) //判斷一下引用變量a所指向的對象是不是一個Cat類的對象(實例) {Cat c = (Cat)a; //向下轉型,強制進行,將對象還原成原本的類型。c.catchMouse(); //當成一個子類對象(貓類對象)進行使用,調用子類(貓)的//特有方法。 } if(a instanceof Dog) {Dog d =(Dog)a;d.watchHome(); } 總結: 所謂的轉型就是指轉換類型,轉換對象扮演的角色,轉換對象存在的狀態。使用對象的另一種
形態,或者另一種角色,或者對象的另一種存在形式,就是將子類對象當成祖先類對象來使用
。
一個對象,可以扮演很多角色(可以擁有很多種存在狀態,可以有很多種體現形態),可以
當成很多種類型的對象來使用,一個對象可以當成本類,父類,祖父類,所有祖先類的對象
來使用??梢赃@樣做的原因是因為該對象繼承了所有祖先類,擁有祖先類的所有內容,一個
最先類對象有的東西,它全有,它可以完美的扮演祖先對象,完美的當成祖先對象使用,
而不會出現任何差錯。
(有人可能覺得它和祖先類不一樣,對于覆蓋的方法而言,他們執行
的不是同一個方法,方法的內容不同,所以不能當成祖先類的對象來使用,這樣思考是錯誤
的,因為類的封裝(函數)特性本身就是對外屏蔽了方法的實現過程,只暴露出方法的
調用接口,只暴露出功能定義,隱藏功能內容,所以對于外界而言,子類對象擁有一樣的
功能定義,那么就可以像調用祖先類對象那樣使用子類對象,就可以將子類對象當成祖先類
對象使用)
向上轉型,之所以是自動轉換,這個“自動轉換”不過是大家起的一個名字,以示和向下轉型
的強制轉換相區分,不存在什么“自動”“不自動”“強制”“不強制”,本質上是指不需要標識出
要轉換的目標類型,是因為將一個子類的引用
賦值給父類的引用是否是合法的,只需要根據賦值號=兩端的類型(值類型,變量類型
)就可以判斷出這樣賦值正確與否。值類型是子類類型,變量類型是父類類型,沒問題,
完全正確。子類對象可以當成父類對象來去使用。
向下轉型,之所以要強制轉換,這個“強制轉換”不過是大家起的一個名字,以示和向上轉型
的自動轉換區分。不存在什么“強制”“不強制”“自動”“不自動”,本質上是指需要標識出要轉
換的目標類型,因為將一個父類引用賦值給一個子類引用是否是合法的,只通過賦值號=
兩端的類型(值類型,變量類型)來檢查,不能判斷出來正確與否,必須要通過查看賦值
號右側值類型所代表的對象能否當成左側的類型的對象來去使用??梢詣t賦值是合法的,
否則非法,出錯。所以Cat c = (Cat)a ;中a前面的(cat)就是用來讓JVM檢查a指向的對象
是否可以當成Cat對象來使用,若可以,則賦值成功,否則出錯。而語法規定必須要加上
(Cat)去提醒JVM檢查,即時是已經用instanceof判斷過了,也要加上(Cat)。一個父類有
多個子類決定了這一點。
new 類名();//表達式返回的是對象的首地址(即對象的句柄,對象的引用)。這個地址的
//數據類型是對象的類的類型。同一個數據類型不同,解釋不同。
*/
abstract class Animal {abstract void eat(); } class Cat extends Animal {public void eat(){System.out.println("吃魚");}public void catchMouse(){System.out.println("抓老鼠");} } class Dog extends Animal {public void eat(){System.out.println("吃骨頭");}public void watchHome(){System.out.println("看家");} } class Pig extends Animal {public void eat(){System.out.println("吃飼料");}public void run(){System.out.println("豬跑步!");} }class Polymorphism {public static void main(String[] args) {Animal a = new Cat();feedAnimal(a);feedAnimal(new Dog());feedAnimal(new Pig());}public static void feedAnimal(Animal a)//Animal a = new Cat();多態的使用{a.eat();if(a instanceof Cat){Cat c = (Cat)a;c.catchMouse();}if(a instanceof Dog){Dog d = (Dog)a;d.watchHome();}if(a instanceof Pig){Pig p = (Pig)a;p.run();}} }
轉載于:https://www.cnblogs.com/wllbelief-win/p/4354062.html
總結
- 上一篇: 30、自定义gridview
- 下一篇: Memcached使用手册