標籤:
/*
一、多態:多態是指一個事物的多種存在狀態(一個事物的多種體現形態或者一個事物的多種表現形態)
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、一個生動的例子:
一個父親:老王,一個兒子:小王。“老王”,“小王”都是名字。但是這個名字
表示出了哪個是父親,哪個是兒子,父親是一種類型,代表的是老一代,兒子是另一種類型,代表
的是新一代。即
兒子繼承父親。
class 父親
{
void 講課()
{
s.o.p(工商管理);
}
}
class 兒子 extends 父親
{
void 講課()//覆蓋了父類(父親)中的方法“講課”
{
s.o.p(Java編程);
}
void 看電影()
{
s.o.p(看電影);
}
}
父親 老王 = new 父親();
兒子 小王 = new 兒子();
這時候,認識父親的老李來了,老李老找父親老王去講課,講工商管理,父親不在,兒子小王
在家,這時,兒子小王化妝成父親老王(將子類對象賦值給父類引用),開門見到老李,
老李拉著老王(小王化妝的)就走了,到了教室裡,老李讓老王開始講課吧(老李調用老王的
講課方法,即“老王.講課()”),老王(兒子小王)上了講台就開始講課,一開口講的是
“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();
}
總結:所謂的轉型就是指轉換類型,轉換對象扮演的角色,轉換對象存在的狀態。使用對象的另一種
形態,或者另一種角色,或者對象的另一種存在形式,就是將子類對象當成祖先類對象來使用
。
一個對象,可以扮演很多角色(可以擁有很多種存在狀態,可以有很多種體現形態),可以
當成很多種類型的對象來使用,一個對象可以當成本類,父類,祖父類,所有祖先類的對象
來使用。可以這樣做的原因是因為該對象繼承了所有祖先類,擁有祖先類的所有內容,一個
最先類對象有的東西,它全有,它可以完美的扮演祖先對象,完美的當成祖先對象使用,
而不會出現任何差錯。
(有人可能覺得它和祖先類不一樣,對於覆蓋的方法而言,他們執行
的不是同一個方法,方法的內容不同,所以不能當成祖先類的對象來使用,這樣思考是錯誤
的,因為類的封裝(函數)特性本身就是對外屏蔽了方法的實現過程,只暴露出方法的
調用介面,只暴露出功能定義,隱藏功能內容,所以對於外界而言,子類對象擁有一樣的
功能定義,那麼就可以像調用祖先類對象那樣使用子類對象,就可以將子類對象當成祖先類
對象使用)
向上轉型,之所以是自動轉換,這個“自動轉換”不過是大家起的一個名字,以示和向下轉型
的強制轉換相區分,不存在什麼“自動”“不自動”“強制”“不強制”,本質上是指不需要標識出
要轉換的目標類型,是因為將一個子類的引用
賦值給父類的引用是否是合法的,只需要根據賦值號=兩端的類型(實值型別,變數類型
)就可以判斷出這樣賦值正確與否。實值型別是子類類型,變數類型是父類類型,沒問題,
完全正確。子類對象可以當成父類對象來去使用。
向下轉型,之所以要強制轉換,這個“強制轉換”不過是大家起的一個名字,以示和向上轉型
的自動轉換區分。不存在什麼“強制”“不強制”“自動”“不自動”,本質上是指需要標識出要轉
換的目標類型,因為將一個父類引用賦值給一個子類引用是否是合法的,只通過賦值號=
兩端的類型(實值型別,變數類型)來檢查,不能判斷出來正確與否,必須要通過查看賦值
號右側實值型別所代表的對象能否當成左側的類型的對象來去使用。可以則賦值是合法的,
否則非法,出錯。所以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();
}
}
}
java多態總結