Java Challengers#3:多型性和繼承
根據Venkat Subramaniam的傳說,多型性是面向物件程式設計中最重要的概念。多型性 -或者物件基於其型別執行專門操作的能力 - 是使Java程式碼具有靈活性的原因。命令,觀察者,裝飾者,策略等設計模式以及Gang Of Four建立的許多其他模式都使用某種形式的多型性。掌握這一概念極大地提高了您通過解決方案來應對程式設計挑戰的能力。
獲取程式碼
多型中的介面和繼承
有了這個Java挑戰者,我們專注於多型和繼承之間的關係。要記住的主要事情是多型性需要繼承或介面實現。您可以在下面的示例中看到這一點,其中包括Duke和Juggy:
<span style="color:#000000"><code> public abstract class JavaMascot { public abstract void executeAction(); } public class Duke extends JavaMascot { @Override public void executeAction() { System.out.println("Punch!"); } } public class Juggy extends JavaMascot { @Override public void executeAction() { System.out.println("Fly!"); } } public class JavaMascotTest { public static void main(String... args) { JavaMascot dukeMascot = new Duke(); JavaMascot juggyMascot = new Juggy(); dukeMascot.executeAction(); juggyMascot.executeAction(); } } </code></span>
此程式碼的輸出將是:
<span style="color:#000000"><code>
Punch!
Fly!
</code></span>
由於其特定的實現,既Duke
與Juggy
人的行為將被執行。
方法是否過載多型?
許多程式設計師對多型與方法重寫和方法過載的關係感到困惑。實際上,只有方法覆蓋才是真正的多型性。過載共享相同的方法名稱,但引數不同。多型性是一個廣義的術語,所以總是會討論這個主題。
多型的目的是什麼?
使用多型的巨大優勢和目的是將客戶端類與實現程式碼分離。客戶端類接收實現以執行必要的操作,而不是硬編碼。通過這種方式,客戶端類知道足以執行其操作,這是鬆散耦合的一個示例。
為了更好地理解多型的目的,請看一下SweetCreator
:
<span style="color:#000000"><code> public abstract class SweetProducer { public abstract void produceSweet(); } public class CakeProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Cake produced"); } } public class ChocolateProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Chocolate produced"); } } public class CookieProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Cookie produced"); } } public class SweetCreator { private List<SweetProducer> sweetProducer; public SweetCreator(List<SweetProducer> sweetProducer) { this.sweetProducer = sweetProducer; } public void createSweets() { sweetProducer.forEach(sweet -> sweet.produceSweet()); } } public class SweetCreatorTest { public static void main(String... args) { SweetCreator sweetCreator = new SweetCreator(Arrays.asList(new CakeProducer(), new ChocolateProducer(), new CookieProducer())); sweetCreator.createSweets(); } } </code></span>
在此示例中,您可以看到SweetCreator
該類只知道SweetProducer
該類。它不知道每個的實現Sweet
。這種分離使我們能夠靈活地更新和重用我們的類,並使程式碼更易於維護。在設計程式碼時,始終尋找使其儘可能靈活和可維護的方法。多型性是一種非常強大的技術,可用於這些目的。
方法覆蓋中的協變返回型別
如果它是協變型別,則可以更改重寫方法的返回型別。一個協變型基本上是返回型別的子類。考慮一個例子:
<span style="color:#000000"><code>
public abstract class JavaMascot {
abstract JavaMascot getMascot();
}
public class Duke extends JavaMascot {
@Override
Duke getMascot() {
return new Duke();
}
}
</code></span>
因為Duke
是a JavaMascot
,我們可以在覆蓋時更改返回型別。
與核心Java類的多型性
我們一直在核心Java類中使用多型。一個非常簡單的例子是當我們例項化ArrayList
將List
介面宣告 為型別的類時:
<span style="color:#000000"><code>
List<String> list = new ArrayList<>();
</code></span>
為了更進一步,請使用不帶多型性的Java Collections API來考慮此程式碼示例:
<span style="color:#000000"><code>
public class ListActionWithoutPolymorphism {
// Example without polymorphism
void executeVectorActions(Vector<Object> vector) {/* Code repetition here*/}
void executeArrayListActions(ArrayList<Object> arrayList) {/*Code repetition here*/}
void executeLinkedListActions(LinkedList<Object> linkedList) {/* Code repetition here*/}
void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList<Object> copyOnWriteArrayList)
{ /* Code repetition here*/}
}
public class ListActionInvokerWithoutPolymorphism {
listAction.executeVectorActions(new Vector<>());
listAction.executeArrayListActions(new ArrayList<>());
listAction.executeLinkedListActions(new LinkedList<>());
listAction.executeCopyOnWriteArrayListActions(new CopyOnWriteArrayList<>());
}
</code></span>
醜陋的程式碼,不是嗎?想象一下試圖保持它!現在看看具有多型性的相同示例:
<span style="color:#000000"><code>
public static void main(String … polymorphism) {
ListAction listAction = new ListAction();
listAction.executeListActions();
}
public class ListAction {
void executeListActions(List<Object> list) {
// Execute actions with different lists
}
}
public class ListActionInvoker {
public static void main(String... masterPolymorphism) {
ListAction listAction = new ListAction();
listAction.executeListActions(new Vector<>());
listAction.executeListActions(new ArrayList<>());
listAction.executeListActions(new LinkedList<>());
listAction.executeListActions(new CopyOnWriteArrayList<>());
}
}
</code></span>
多型性的好處是靈活性和可擴充套件性。我們可以只宣告一個接收泛型List
型別的方法,而不是建立幾個不同的方法。
在多型方法呼叫中呼叫特定方法
可以在多型呼叫中呼叫特定方法,但這樣做是以犧牲靈活性為代價的。這是一個例子:
<span style="color:#000000"><code>
public abstract class MetalGearCharacter {
abstract void useWeapon(String weapon);
}
public class BigBoss extends MetalGearCharacter {
@Override
void useWeapon(String weapon) {
System.out.println("Big Boss is using a " + weapon);
}
void giveOrderToTheArmy(String orderMessage) {
System.out.println(orderMessage);
}
}
public class SolidSnake extends MetalGearCharacter {
void useWeapon(String weapon) {
System.out.println("Solid Snake is using a " + weapon);
}
}
public class UseSpecificMethod {
public static void executeActionWith(MetalGearCharacter metalGearCharacter) {
metalGearCharacter.useWeapon("SOCOM");
// The below line wouldn't work
// metalGearCharacter.giveOrderToTheArmy("Attack!");
if (metalGearCharacter instanceof BigBoss) {
((BigBoss) metalGearCharacter).giveOrderToTheArmy("Attack!");
}
}
public static void main(String... specificPolymorphismInvocation) {
executeActionWith(new SolidSnake());
executeActionWith(new BigBoss());
}
}
</code></span>
我們在這裡使用的技術是在執行時轉換或故意更改物件型別。
請注意,僅在將泛型型別轉換為特定型別時才可以呼叫特定方法。一個很好的類比就是明確地向編譯器說:“嘿,我知道我在這裡做了什麼,所以我要將物件轉換為特定的型別並使用特定的方法。”
參考上面的例子,編譯器拒絕接受特定的方法呼叫有一個重要原因:正在傳遞的類可能是SolidSnake
。在這種情況下,編譯器無法確保宣告MetalGearCharacter
了giveOrderToTheArmy
方法的每個子類。
該instanceof
保留的關鍵字
注意保留字instanceof
。在呼叫特定方法之前,我們詢問是否MetalGearCharacter
為“ instanceof
” BigBoss
。如果它不是一個BigBoss
例項,我們會收到下面的異常訊息:
<span style="color:#000000"><code>
Exception in thread "main" java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake cannot be cast to com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss
</code></span>
該super
保留的關鍵字
如果我們想從Java超類引用屬性或方法怎麼辦?在這種情況下,我們可以使用super
保留字。例如:
<span style="color:#000000"><code>
public class JavaMascot {
void executeAction() {
System.out.println("The Java Mascot is about to execute an action!");
}
}
public class Duke extends JavaMascot {
@Override
void executeAction() {
super.executeAction();
System.out.println("Duke is going to punch!");
}
public static void main(String... superReservedWord) {
new Duke().executeAction();
}
}
</code></span>
使用保留字super
中Duke
的executeAction
方法呼叫超方法。然後我們執行特定的操作Duke
。這就是為什麼我們可以在下面的輸出中看到這兩條訊息:
<span style="color:#000000"><code>
The Java Mascot is about to execute an action!
Duke is going to punch!
</code></span>
採取多型性挑戰!
讓我們試一下你所學到的關於多型和繼承的知識。在這個挑戰中,您將獲得Matt Groening的“辛普森一家”中的一些方法,您的挑戰是推斷出每個類的輸出結果。首先,請仔細分析以下程式碼:
<span style="color:#000000"><code>
public class PolymorphismChallenge {
static abstract class Simpson {
void talk() {
System.out.println("Simpson!");
}
protected void prank(String prank) {
System.out.println(prank);
}
}
static class Bart extends Simpson {
String prank;
Bart(String prank) { this.prank = prank; }
protected void talk() {
System.out.println("Eat my shorts!");
}
protected void prank() {
super.prank(prank);
System.out.println("Knock Homer down");
}
}
static class Lisa extends Simpson {
void talk(String toMe) {
System.out.println("I love Sax!");
}
}
public static void main(String... doYourBest) {
new Lisa().talk("Sax :)");
Simpson simpson = new Bart("D'oh");
simpson.talk();
Lisa lisa = new Lisa();
lisa.talk();
((Bart) simpson).prank();
}
}
</code></span>
你怎麼看?最終的輸出是什麼?不要使用IDE來解決這個問題!關鍵是要提高程式碼分析技能,所以儘量確定自己的輸出。
選擇你的答案,你將能夠在下面找到正確的答案。
<span style="color:#000000"><code>
A) I love Sax!
D'oh
Simpson!
D'oh
B) Sax :)
Eat my shorts!
I love Sax!
D'oh
Knock Homer down
C) Sax :)
D'oh
Simpson!
Knock Homer down
D) I love Sax!
Eat my shorts!
Simpson!
D'oh
Knock Homer down
</code></span>
剛剛發生了什麼?瞭解多型性
對於以下方法呼叫:
<span style="color:#000000"><code>
new Lisa().talk("Sax :)");
</code></span>
輸出將是“ I love Sax!
”這是因為我們傳遞String
給方法並Lisa
具有方法。
對於下一次呼叫:
<span style="color:#000000"><code>
Simpson simpson = new Bart("D'oh");</code>
<code>simpson.talk();
</code></span>
輸出將是“ Eat my shorts!
”這是因為我們正在例項化Simpson
型別Bart
。
現在檢查一下,這有點棘手:
<span style="color:#000000"><code>
Lisa lisa = new Lisa();
lisa.talk();
</code></span>
在這裡,我們使用繼承方法過載。我們沒有向talk方法傳遞任何內容,這Simpson
talk
就是呼叫該方法的原因。在這種情況下,輸出將是:
<span style="color:#000000"><code>
"Simpson!"
</code></span>
還有一個:
<span style="color:#000000"><code>
((Bart) simpson).prank();
</code></span>
在這種情況下,prank String
當我們用Bart
類例項化時傳遞了new Bart("D'oh");
。在這種情況下,首先super.prank
呼叫該方法,然後呼叫特定的prank
方法Bart
。輸出將是:
<span style="color:#000000"><code>
"D'oh"
"Knock Homer down"
</code></span>
多型性常見錯誤
認為可以在不使用強制轉換的情況下呼叫特定方法是一種常見錯誤。
另一個錯誤是不確定在以多型方式例項化類時將呼叫哪種方法。請記住,要呼叫的方法是建立的例項的方法。
還要記住,方法重寫不是方法過載。
如果引數不同,則無法覆蓋方法。這是可能的改變過載方法的返回型別如果返回型別為超類方法的子類。
什麼要記住多型性
- 建立的例項將確定使用多型時將呼叫哪種方法。
- 的
@Override
註釋責成使用一個重寫方法程式設計師; 如果沒有,則會出現編譯錯誤。 - 多型性可以與普通類,抽象類和介面一起使用。
- 大多數設計模式依賴於某種形式的多型性。
- 在多型子類中使用特定方法的唯一方法是使用強制轉換。
- 可以使用多型在程式碼中設計一個強大的結構。
- 執行測試。這樣做,您將能夠掌握這個強大的概念!
回答金鑰
這個問題的答案Java的挑戰者是d。輸出將是:
<span style="color:#000000"><code>
I love Sax!
Eat my shorts!
Simpson!
D'oh
Knock Homer down
</code></span>