Java多態是如何實現的?
Java的多態和C++一樣,是透過延時綁定(late binding)或說執行時間綁定(runtime binding)來實現的。當呼叫某一個物件所引用的方法時,因為編譯器並不知道這個引用到底指向的是變數宣告時所說明的型別對象,還是該型別子類別的對象。因此編譯器無法為這次呼叫綁定到具體的某個方法。只有透過java中的運行時類型識別(RTTI, Runtime type identification)在運行時綁定到具體的方法。以下是一個具體的例子:
class shape { public void draw() { print("shape"); } } class triangle extends shape { public void draw() { print("triangle"); } } public class Polymorphism { public static void main(String[] args) { shape s=new triangle(); s.draw(); }
結果是triangle
s是一個shape引用,但是在運行時因為是triangle對象,所以還是調用了triangle的draw方法。
Java多態中的一些陷阱
重寫私有方法?
Java裡面是不能重寫私有方法的,這個其實很好理解,因為私有方法在子類別中是不可見的。子類別沒有繼承父類別的私有方法,更談不上重寫了。因此在子類別中的同名方法是一個全新的方法。
public class Polymorphism { private void show() { print("show parent"); } public static void main(String[] args) { Polymorphism p=new privateMethod(); p.show(); } } class privateMethod extends Polymorphism { public void show() { print("show derived"); } }
結果是 show parent
字段和靜態方法的多態?
子類別可以繼承父類別的非私有字段,子類別的字段是否也具有多態性呢?讓我們來看一個實際的例子:
class shape { protected int perimeter=1; public void draw() { print("shape"); } public int getPerimeter() { return perimeter; } } class triangle extends shape { int perimeter=3; public void draw() { print("triangle"); } public int getPerimeter() { return perimeter; } public int getSuperPerimeter() { return super.perimeter; } } public class Polymorphism { public static void main(String[] args) { shape s=new triangle(); print("s.perimeter:"+s.perimeter); print("s.getperimeter:"+s.getPerimeter()); triangle t=new triangle(); print("t.perimeter:"+t.perimeter); print("t.getperimeter:"+t.getPerimeter()); print("t.getsuperperimeter:"+t.getSuperPerimeter()); } }
這個運行結果包含了以下資訊:
1.triangle物件向上轉型成shape後字段直接存取都是由編譯器確定的,因此不會表現出多態性,返回的是1。
2.triangle物件向上轉型成shape後呼叫方法存取字段是根據運行時物件類型延時綁定呼叫了triangle的getperimeter方法,傳回的是3
3.t物件中包含了兩個perimeter字段,一個來自於他本身,一個來自於他的父類。同時用字段名去呼叫該字段時預設回傳的是他本身的perimeter字段,要呼叫從父類繼承的該字段,要用super.perimeter的方法。
這個結果看起來多多少少讓人有些疑惑,為了避免這種情況出現,我們一般都把字段聲明為private(子類就無法繼承),同時我們在子類中聲明的字段最好不要與從父類別繼承的欄位同名。
靜態方法是沒有多態性的,因為靜態方法是和類別綁定的,不會存在不知道具體類型的情況。
構造函數的多態性?
構造函數是不具有多態性的,因為構造方法本身是靜態方法(如果不是的話,就會陷入雞生蛋,蛋生雞的死循環了)。要引入我們的問題,先來看看建構函數的呼叫順序。
1.為這個物件分配的儲存空間都被初始化為0(物件初始化為null)
2.父類別的建構函式呼叫(這樣才能保證在子類別的建構函式中存取的欄位被初始化)
3.成員變數初始化
4.子類別的建構子呼叫
現在假設如果在第二步驟中,我們在父類別的建構子裡呼叫了某個方法,這個方法是不是多型的?還是來看一個具體的例子:
class shape { protected int perimeter=1; public shape() { draw(); print("shape created"); } public void draw() { print("draw shape "+perimeter); } } class triangle extends shape { int perimeter=3; public triangle() { print("triangle created"); } public void draw() { print("draw triangle "+perimeter); } public int getPerimeter() { return perimeter; } } public class Polymorphism { public static void main(String[] args) { shape s=new triangle(); } }
我們可以看到雖然triangle物件還沒有建構完畢,draw方法仍是動態綁定到了triangle的draw方法。同時注意到perimeter的值還沒有初始化為3,而是0。
這樣的結果就是我們在triangle物件還沒有被初始化之前就訪問了其中的欄位。因此我們在實際應用中要避免在建構函式中呼叫其他方法,或只呼叫私有方法(不會被繼承,因此不會引發該問題)
更多Java多態性的使用注意事項相關文章請關注PHP中文網!