Javaチャレンジャー#3:ポリモーフィズムと継承
Javaの問題に関する一連の記事の翻訳を続けています。 行についての最後の投稿は驚くほど熱い議論を引き起こしました。 あなたもこの記事を通り過ぎないことを願っています。 そして、はい-Java 開発者コースの10周年記念ストリームに招待しています。
伝説のVenkat Subramaniamによると、多型はオブジェクト指向プログラミングで最も重要な概念です。 多態性、またはオブジェクトのタイプに基づいて特殊なアクションを実行する能力は、Javaコードを柔軟にするものです。 コマンド、オブザーバー、デコレーター、ストラテジーなどのデザインパターン、およびGang Of Fourによって作成された他の多くのパターンはすべて、何らかの形のポリモーフィズムを使用しています。 この概念をマスターすると、ソフトウェアソリューションを介して考える能力が大幅に向上します。
この記事のソースコードを取得して、ここで実験できます: https : //github.com/rafadelnero/javaworld-challengers
ポリモーフィズムのインターフェイスと継承
この記事では、ポリモーフィズムと継承の関係に焦点を当てます。 心に留めておくべき主なことは、ポリモーフィズムがインターフェイスの 継承または実装を必要とすることです。 これは、以下の例でDukeとJuggy
を使用して確認できます。
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(); } }
このコードの出力は次のようになります。
Punch! Fly!
特定の実装が定義されているため、 Duke
メソッドとJuggy
メソッドの両方が呼び出されます。
メソッドは多型をオーバーロードしていますか? 多くのプログラマーは、ポリモーフィズムとオーバーライドおよびオーバーロードの関係を混同しています 。 実際、メソッドのオーバーライドのみが真のポリモーフィズムです。 オーバーロードでは、同じメソッド名を使用しますが、パラメーターは異なります。 多態性は広義の用語であるため、このトピックについては常に議論が行われます。
多型の目的は何ですか
ポリモーフィズムを使用する大きな利点と目的は、実装とのクライアントクラスの接続を減らすことです。 ハードコーディングの代わりに、クライアントクラスは依存関係の実装を受け取り、必要なアクションを実行します。 したがって、クライアントクラスは、アクションを実行するための最小値を知っています。これは、弱いバインディングの例です。
ポリモーフィズムの目的をよりよく理解するには、 SweetCreator
。
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(); } }
この例では、 SweetCreator
クラスはSweetCreator
クラスSweetCreator
知っていることがSweetProducer
ます。 彼はすべてのSweet
の実装を知りません。 この分離により、クラスを更新および再利用する柔軟性が得られ、これによりコードの保守がはるかに簡単になります。 コードを設計するときは、可能な限り柔軟で便利な方法を常に探してください。 ポリモーフィズムは、この目的に使用する非常に強力な方法です。
@Override
は、プログラマが同じメソッドシグネチャを使用する必要がありますが、オーバーライドする必要があります。 メソッドがオーバーライドされない場合、コンパイルエラーが発生します。
メソッドをオーバーライドするときの共変の戻り値の型
共変型の場合、オーバーライドされたメソッドの戻り値の型を変更できます。 共変型は、基本的に戻り値のサブクラスです。
例を考えてみましょう:
public abstract class JavaMascot { abstract JavaMascot getMascot(); } public class Duke extends JavaMascot { @Override Duke getMascot() { return new Duke(); } }
Duke
はJavaMascot
であるため、オーバーライド時に戻り値の型を変更できます。
Java基本クラスの多態性
Javaベースクラスでは常にポリモーフィズムを使用します。 非常に簡単な例の1つは、 List
インターフェイスとして型宣言を使用してArrayList
クラスをインスタンス化することArrayList
。
List<String> list = new ArrayList<>();
多態性のない Java Collections API を使用したサンプルコードを考えてみましょう。
public class ListActionWithoutPolymorphism { // void executeVectorActions(Vector<Object> vector) {/* */} void executeArrayListActions(ArrayList<Object> arrayList) {/* */} void executeLinkedListActions(LinkedList<Object> linkedList) {/* */} void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList<Object> copyOnWriteArrayList) { /* */} } public class ListActionInvokerWithoutPolymorphism { listAction.executeVectorActions(new Vector<>()); listAction.executeArrayListActions(new ArrayList<>()); listAction.executeLinkedListActions(new LinkedList<>()); listAction.executeCopyOnWriteArrayListActions(new CopyOnWriteArrayList<>()); }
嫌なコードですよね? 彼に同行する必要があると想像してください! 次に、ポリモーフィズムを使用した同じ例を考えます。
public static void main(String... polymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(); } public class ListAction { void executeListActions(List<Object> list) { // } } 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<>()); } }
多態性の利点は、柔軟性と拡張性です。 いくつかの異なるメソッドを作成する代わりに、 List
型を取得する1つのメソッドを宣言できます。
多相メソッドの特定のメソッドを呼び出す
多態的なメソッド呼び出しで特定のメソッドを呼び出すことができます。これは柔軟性のためです。 以下に例を示します。
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"); // // metalGearCharacter.giveOrderToTheArmy("Attack!"); if (metalGearCharacter instanceof BigBoss) { ((BigBoss) metalGearCharacter).giveOrderToTheArmy("Attack!"); } } public static void main(String... specificPolymorphismInvocation) { executeActionWith(new SolidSnake()); executeActionWith(new BigBoss()); } }
ここで使用する手法は、実行時にオブジェクトの型をキャストまたは意識的に変更することです。
特定のメソッドを呼び出すことは、より一般的な型をより特定の型にキャストする場合にのみ可能であることに注意してください。 いい例えは、コンパイラに明示的に伝えることです。「ねえ、私はここで何をしているのかわかっているので、オブジェクトを特定の型にキャストし、このメソッドを使用します。」
上記の例を参照すると、コンパイラには特定のメソッドの呼び出しを受け入れない正当な理由があります。渡されるクラスはSolidSnake
なければなりません。 この場合、コンパイラーには、 MetalGearCharacter
各サブクラスにMetalGearCharacter
があることを確認する方法がありません。
Instanceofキーワード
予約語instanceof
注意してください。 特定のメソッドを呼び出す前に、 MetalGearCharacter
インスタンスかどうかを尋ねBigBoss
。 これがBigBoss
インスタンスではない場合、次の例外が発生します。
Exception in thread `main" java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake cannot be cast to com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss
super
キーワード
親クラスから属性またはメソッドを参照したい場合はどうしますか? この場合、 super
キーワードを使用できます。
例:
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(); } }
Duke
クラスのexecuteAction
メソッドで予約語super
を使用すると、親クラスのメソッドが呼び出されます。 次に、 Duke
クラスから特定のアクションを実行します。 これが出力で両方のメッセージを見ることができる理由です:
The Java Mascot is about to execute an action! Duke is going to punch!
多型の問題を解決する
ポリモーフィズムと継承について学んだことを確認しましょう。
この問題では、Matt GroeningのThe Simpsonsからいくつかのメソッドが与えられます。Wavsからは、各クラスの結論がどうなるかを解明する必要があります。 まず、次のコードを慎重に分析します。
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(); } }
どう思いますか? 結果はどうなりますか? IDEを使用して調べないでください! 目標は、コード分析スキルを向上させることですので、自分で判断してください。
答えを選択してください(記事の最後で正しい答えを見つけることができます)。
A)
サックス大好き!
ドー
シンプソン!
ドー
B)
サックス:)
ショートパンツを食べて!
サックス大好き!
ドー
ホーマーダウンノック
C)
サックス:)
ドー
シンプソン!
ホーマーダウンノック
D)
サックス大好き!
ショートパンツを食べて!
シンプソン!
ドー
ホーマーダウンノック
どうした 多態性を理解する
次のメソッド呼び出しの場合:
new Lisa().talk("Sax :)");
出力は「I love Sax!」になります。 これは、文字列をメソッドに渡し、 Lisa
クラスにそのようなメソッドがあるためです。
次の呼び出しの場合:
Simpson simpson = new Bart("D'oh"); simpson.talk();
出力は「Eat my shorts!」になります。 これは、 Simpson
型をBart
初期化するためです。
見てください、これは少し複雑です:
Lisa lisa = new Lisa(); lisa.talk();
ここでは、継承を使用したメソッドのオーバーロードを使用します。 talk
メソッドには何も渡しませんので、 Simpson
のtalk
メソッドSimpson
呼び出されます。
この場合、出力は「Simpson!」になります。
ここに別のものがあります:
((Bart) simpson).prank();
この場合、 new Bart("D'oh");
を通じてBart
クラスをインスタンス化するときにprank
文字列が渡されましたnew Bart("D'oh");
。 この場合、最初にsuper.prank()
メソッドが呼び出され、次にBart
クラスのprank()
メソッドが呼び出されます。 結論は次のとおりです。
"D'oh" "Knock Homer down"
一般的な多型エラー
よくある間違いは、型キャストを使用せずに特定のメソッドを呼び出すことができると考えることです。
もう1つの間違いは、クラスが多相的にインスタンス化されるときにどのメソッドが呼び出されるかに関する不確実性です。 呼び出されたメソッドは、インスタンス化されたインスタンスのメソッドであることに注意してください。
また、メソッドのオーバーライドはメソッドのオーバーロードではないことを忘れないでください。
パラメーターが異なる場合、メソッドをオーバーライドできません。 戻り値の型がサブクラスの場合、オーバーライドされたメソッドの戻り値の型を変更できます。
ポリモーフィズムについて覚えておくべきこと
作成されたインスタンスは、ポリモーフィズムを使用するときに呼び出されるメソッドを決定します。
@Override
は、プログラマーがオーバーライドされたメソッドを使用する必要があります。 そうしないと、コンパイラエラーが発生します。
多態性は、通常のクラス、抽象クラス、およびインターフェースで使用できます。
ほとんどの設計パターンは、何らかの形の多態性に依存しています。
多態性サブクラスでメソッドを呼び出す唯一の方法は、型キャストを使用することです。
ポリモーフィズムを使用して、強力なコード構造を作成できます。
実験。 これにより、この強力なコンセプトをマスターできるようになります!
答え
答えはDです。
結論は次のとおりです。
I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down
いつものように、私はあなたのコメントと質問を歓迎します。 そして、私たちは公開レッスンでVitalyを待っています 。