“朋友,你喜歡鯨魚嗎?”
“鯨魚?沒有,從沒聽說過。”
“就像牛,只是體型更大,會游泳。順便說一下,鯨魚是牛進化而來的。呃,至少它們有一個共同的祖先。沒關係。”
“聽好了。我想告訴你另一個非常強大的 OOP 工具:多態性。它有四個特性。”
1) 方法覆蓋。
想像一下,您已經為遊戲編寫了一個“Cow”類。它有很多成員變量和方法。這個類的對象可以做各種事情:走路、吃飯、睡覺。奶牛走路時也會搖鈴。假設您已經將課程中的所有內容都實現到了最小的細節。
然後客戶突然說他想發布一個新的遊戲關卡,所有的動作都發生在海裡,主角是鯨魚。
您開始設計 Whale 類並意識到它與 Cow 類只有細微差別。這兩個類使用非常相似的邏輯,您決定使用繼承。
Cow 類非常適合作為父類:它已經擁有所有必要的變量和方法。您需要做的就是增加鯨魚的游泳能力。但有一個問題:你的鯨魚有腿、角和鈴鐺。畢竟,Cow 類實現了這個功能。你能做什麼?
方法覆蓋來拯救。如果我們繼承的方法不能完全滿足我們在新類中的需要,我們可以用另一個方法替換該方法。
這是怎麼做到的?在我們的後代類中,我們聲明了我們想要更改的方法(使用與父類中相同的方法簽名)。然後我們為該方法編寫新代碼。就是這樣。就好像父類的舊方法不存在一樣。
它是這樣工作的:
代碼 | 描述 |
---|---|
|
這裡我們定義了兩個類: Cow 和 Whale 。 Whale 繼承 Cow 。
該類 |
|
此代碼在屏幕上顯示“我是牛”。 |
|
此代碼在屏幕上顯示“我是鯨魚” |
在繼承Cow
和重寫之後printName
,該類Whale
實際上有如下數據和方法:
代碼 | 描述 |
---|---|
|
我們對任何舊方法一無所知。 |
“老實說,這就是我所期待的。”
2)但這還不是全部。
“假設該類 Cow
有一個 printAll
調用其他兩個方法的方法。那麼代碼將像這樣工作:”
屏幕會顯示:
我是白人
我是鯨魚
代碼 | 描述 |
---|---|
|
|
|
屏幕會顯示: 我是白人 我是鯨魚 |
請注意,當在 Whale 對像上調用 Cow 類的 printAll () 方法時,將使用 Whale 的 printName() 方法,而不是 Cow 的。
重要的不是編寫該方法的類,而是調用該方法的對象的類型(類)。
“我懂了。”
“您只能繼承和覆蓋非靜態方法。靜態方法不會被繼承,因此不能被覆蓋。”
這是我們應用繼承並覆蓋方法後 Whale 類的樣子:
代碼 | 描述 |
---|---|
|
這是我們應用繼承並覆蓋方法後 Whale 類的樣子。我們對任何舊方法一無所知printName 。 |
3) 類型鑄造。
這是一個更有趣的觀點。因為一個類繼承了父類的所有方法和數據,所以這個類的一個對象可以被父類的變量引用(還有父類的父類等等,一直到Object類)。考慮這個例子:
代碼 | 描述 |
---|---|
|
屏幕會顯示: 我是白人。 |
|
屏幕會顯示: 我是白人。 |
|
屏幕會顯示: Whale@da435a。 toString() 方法繼承自 Object 類。 |
“好東西。但你為什麼需要這個?”
“這是一個很有價值的功能。你稍後就會明白,它是非常非常有價值的。”
4) 後期綁定(動態調度)。
這是它的樣子:
代碼 | 描述 |
---|---|
|
屏幕會顯示: 我是鯨魚。 |
|
屏幕會顯示: 我是鯨魚。 |
請注意,決定我們調用哪個特定printName方法(Cow 或 Whale 類的方法)的不是變量的類型,而是變量引用的對象的類型。
Cow變量存儲對Whale對象的引用,將調用Whale類中定義的printName方法。
“好吧,為了清楚起見,他們沒有添加。”
“是啊,沒那麼明顯。記住這個重要的規則:”
您可以對變量調用的方法集由變量的類型決定。但是調用哪個特定方法/實現取決於變量引用的對象的類型/類。
“我會盡力。”
“你會經常遇到這個,所以你會很快理解它並且永遠不會忘記。”
5) 類型鑄造。
轉換對於引用類型(即類)的工作方式不同於它對於基本類型的工作方式。但是,擴大和縮小轉換也適用於引用類型。考慮這個例子:
擴大轉換 | 描述 |
---|---|
|
經典的擴大轉換。現在您只能調用 Whale 對象的 Cow 類中定義的方法。 編譯器只會讓你使用cow 變量來調用 Cow 類型定義的那些方法。 |
縮小轉換 | 描述 |
---|---|
|
帶有類型檢查的經典縮小轉換。Cow 類型的 cow變量存儲對 Whale 對象的引用。 我們檢查是否是這種情況,然後執行(擴大)類型轉換。這也稱為類型轉換。 |
|
您還可以在不對對象進行類型檢查的情況下執行引用類型的縮小轉換。 在這種情況下,如果cow變量指向的不是 Whale 對象,則會拋出異常 (InvalidClassCastException)。 |
6)現在來點好吃的。調用原始方法。
有時在覆蓋繼承的方法時,您不想完全替換它。有時您只想添加一點。
在這種情況下,您確實希望新方法的代碼調用相同的方法,但是是在基類上。而 Java 讓你做到這一點。這是它的完成方式: super.method()
。
這裡有些例子:
代碼 | 描述 |
---|---|
|
|
|
屏幕會顯示: 我是白人 這是假的:我是牛 我是鯨魚 |
“嗯。好吧,那是一些教訓。我的機器人耳朵幾乎融化了。”
“是的,這不是簡單的東西。這是你會遇到的一些最難的材料。教授答應提供其他作者的材料鏈接,這樣如果你還有什麼不明白的地方,你可以填寫差距。”
GO TO FULL VERSION