任憲臻 任美玲
(1.北京信息職業技術學院 軟件與信息學院,北京 100018;2.煙臺南山學院 工學院計算機系,山東 煙臺 265700)
在Java 編程中,我們經常把父類與子類之間的繼承關系描述成“is a”關系。比如我們在編程中定義一個父類樹類Tree,再定義一個子類蘋果樹類AppleTree,那么我們完全可以說“蘋果樹是樹”,但是當我們說“蘋果樹是樹”的時候,蘋果樹這個子類就失去了它自身特有的屬性和功能。在Java 編程語言中,體現“蘋果樹是樹”這種說法的就是經常提到的上轉型對象。
在圖1 中,我們定義了AppleTree 是Tree 的子類,那么在實際使用中,我們就可以通過語句Tree appleTree1=new AppleTree(3.5,1.5,“紅富士”);創建一個子類對象,但是跟以往創建對象的語句不同,在這條語句中,是使用父類對象的引用指向創建的子類對象。因為AppleTree 與Tree 這兩個類存在繼承關系,所以這種使用方法是完全正確的,我們稱對象appleTree1 是創建的子類對象(通過new AppleTree(3.5,1.5,“紅富士”)創建得到)的上轉型對象,從這我們可以發現,Java 中對象的上轉型對象的實體是由子類創建的。
在Java 編程語言中,上轉型對象的前提必須是存在繼承關系的兩個或多個類。利用上轉型對象調用方法,可以實現Java 中方法的多態性,這是因為不同的子類重寫父類方法時也會實現不同的行為。
例如,在圖1-3 中,分別定義了父類Shapes 和它的兩個子類Triangle 和Square,在圖4 代碼的27 行和28 行,分別利用父類對象引用triangle 和square(也就是上轉型對象)指向生成的不同的子類,在代碼的29 行和30 行調用方法draw()時,通過輸出結果,我們可以發現雖然都是通過使用父類對象引用調用的draw()方法,但是在實際的方法執行過程中,則是根據指向的具體子類對象,分別調用了子類自己的方法進行輸出,這就是通過上轉型對象實現的方法的多態,這主要是借助于Java 語言的動態綁定來實現。以27 行代碼為例,在代碼的實際執行過程中,Java 虛擬機會去調用同triangl 所指向的對象的實際類型相匹配的方法,因為triangle 指向的實際類型為Shapes 的子類Triangle,而且子類Triangle 中又定義了draw()方法,那么該方法被會被調用,如果在子類中沒有定義draw(),就會去Triangle 的父類中去查找方法draw()。如果子類和父類中都沒有定義draw()方法,那么在程序編譯時就會報錯。還有一種情況,假設在這個程序中,子類Triangle 中沒有定義draw()方法,但是在父類中定義了,那么就會調用父類中定義的draw()方法進行輸出。
因為上轉型對象的實體是由子類創建的,所以上轉型對象在使用時會丟失原來子類對象特有的屬性和方法,換句話說就是上轉型對象是一個簡化后的子類對象,因為這個特性,在使用上轉型對象的時候,需要注意以下幾點:
1.上轉型對象不能操作子類新增的成員變量,也不能調用子類新增的的方法。如下圖5 所示,代碼28 行使用上轉型對象triangle 去給子類Triangle 中新增的成員變量triType 賦值時會報編譯錯誤,代碼29 行使用上轉型對象triangle 去調用子類Triangle 中新增的方法triOnly()也會導致編譯錯誤。這些問題的發生都是因為上轉型對象是被簡化的子類對象,它已經丟失了子類新增的屬性和方法,所以如果想訪問這些新增的屬性和方法時,都不能通過編譯。
2.上轉型對象可以訪問子類從父類繼承的或者隱藏的成員變量,也可以調用子類繼承的方法或者子類重寫的方法。使用上轉型對象調用子類繼承的方法或者是子類重寫的方法時,同使用子類對象去調用這些方法是一樣。所以如果子類重寫了父類的某個方法,那么當使用上轉型對象調用這個方法時,一定是調用的子類重寫的方法。但是如果子類重寫的是父類的靜態方法,這種情況下,子類對象的上轉型對象調用的就不是子類重寫的靜態方法,而是只能調用父類的靜態方法,如下圖6 所示。雖然在代碼的39 行和40 行,分別使用子類Triangle 和Square 子類的上轉型對象triangle 和square 去調用子類重寫的父類的方法prt(),但是因為父類的prt()方法是靜態方法,所以即使子類重寫了這個方法,但是使用上轉型對象去調用的時候調用的不是子類重寫的方法,而依然是父類中的靜態方法,這一點必須要注意。
3.在使用上轉型對象的時候,不能混淆父類創建的對象和子類對象的上轉型對象,這是兩個不同的概念。雖然子類的上轉型對象丟失了子類特有的成員變量和方法,但是我們可以把上轉型對象再次強制轉換為一個具體的子類對象,這時這個強制轉換過來的子類對象又重新具備了子類所有的成員變量和方法。如下圖7 所示,在第28行代碼中,我們把上轉型對象triangle 又強制轉換為一個Triangle 類的對象,這樣在代碼的29 行和30 行,利用triangleTran 對象調用Triangle 類新增的成員變量和方法時,就不會報編譯錯誤。
雖然我們可以將子類的上轉型對象強制轉化為子類對象,但是我們不能將父類創建的對象引用賦值給子類聲明的對象,也不能將父類對象強制轉換為子類對象,就如同我們不能說“樹是蘋果樹”一個道理。如下圖8 所示,代碼28 行將父類對象shape 賦值給子類聲明的對象,此時程序提示編譯錯誤。雖然代碼31 行在編譯時看似沒有任何問題,但是在運行的過程中卻拋出了異常信息:java.lang.ClassCastException: Shapes cannot be cast to Triangle,所以我們在子類的上轉型對象的使用方面一定要注意這方面的問題,不能錯誤賦值或者錯誤強制轉換對象類型。
綜上所述,子類的上轉型對象就是是把子類對象用父類對象引用使用,因此使用子類的上轉型對象的前提必須是多個類之間必須存在著繼承關系,而且上轉型對象在調用方法的時候,只能調用父類中已經定義的方法,如果調用子類的新增方法則會報錯。為了訪問子類新增的方法或者成員變量,我們可以把已經經過上轉型的對象再次強制轉換為子類對象。通過上轉型對象的使用,我們可以實現方法調用的多態性。