Javaのポリモーフィズムと継承

伝説のVenkatSubramaniamによると、ポリモーフィズムはオブジェクト指向プログラミングで最も重要な概念です。ポリモーフィズム(またはオブジェクトがそのタイプに基づいて特殊なアクションを実行する機能)が、Javaコードを柔軟にします。Command、Observer、Decorator、Strategyなど、Gang Of Fourによって作成された多くのデザインパターンはすべて、何らかの形のポリモーフィズムを使用しています。この概念を習得すると、プログラミングの課題に対する解決策を考える能力が大幅に向上します。

コードを取得する

このチャレンジのソースコードを入手して、ここで独自のテストを実行できます://github.com/rafadelnero/javaworld-challengers

ポリモーフィズムにおけるインターフェースと継承

このJavaチャレンジャーでは、ポリモーフィズムと継承の関係に焦点を当てています。覚えておくべき主なことは、ポリモーフィズムには継承またはインターフェースの実装が必要であるということです。これは、デュークとジャギーをフィーチャーした以下の例で確認できます。

 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! 

それらの特定の実装のために、DukeJuggyの両方のアクションが実行されます。

メソッドはポリモーフィズムをオーバーロードしていますか?

多くのプログラマーは、ポリモーフィズムとメソッドのオーバーライドおよびメソッドのオーバーロードとの関係について混乱しています。実際、メソッドのオーバーライドのみが真の多形です。オーバーロードは同じメソッドの名前を共有しますが、パラメーターが異なります。ポリモーフィズムは広義の用語であるため、このトピックについては常に議論があります。

ポリモーフィズムの目的は何ですか?

ポリモーフィズムを使用する大きな利点と目的は、クライアントクラスを実装コードから切り離すことです。クライアントクラスは、ハードコーディングされる代わりに、必要なアクションを実行するための実装を受け取ります。このようにして、クライアントクラスは、そのアクションを実行するのに十分な知識を持っています。これは、疎結合の例です。

ポリモーフィズムの目的をよりよく理解するには、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; public SweetCreator(List 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クラスがクラスのみを知っていることがわかります 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つはArrayListListインターフェイスを型として宣言するクラス  をインスタンス化する場合です。

 List list = new ArrayList(); 

さらに先に進むために、ポリモーフィズムのないJavaコレクションAPI使用したこのコードサンプルを検討してください。

 public class ListActionWithoutPolymorphism { // Example without polymorphism void executeVectorActions(Vector vector) {/* Code repetition here*/} void executeArrayListActions(ArrayList arrayList) {/*Code repetition here*/} void executeLinkedListActions(LinkedList linkedList) {/* Code repetition here*/} void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList copyOnWriteArrayList) { /* Code repetition here*/} } 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 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()); } } 

ポリモーフィズムの利点は、柔軟性と拡張性です。複数の異なるメソッドを作成する代わりに、ジェネリック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"); // 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()); } } 

ここで使用している手法は、実行時にオブジェクトタイプをキャストするか、意図的に変更することです。

ジェネリック型を特定の型にキャストする場合にのみ、特定のメソッドを呼び出すことができることに注意してください。良い例えは、コンパイラに明示的に言うことです。「ねえ、私はここで何をしているのか知っているので、オブジェクトを特定の型にキャストし、特定のメソッドを使用します。」  

上記の例を参照すると、コンパイラが特定のメソッド呼び出しの受け入れを拒否する重要な理由がありますSolidSnake。渡されるクラスはである可能性があります。この場合、コンパイラがのすべてのサブクラスにメソッドが宣言されMetalGearCharacterていることを確認する方法はありませんgiveOrderToTheArmy

instanceof予約されたキーワード

予約語に注意してくださいinstanceof。特定のメソッドを呼び出す前に、MetalGearCharacterinstanceof」であるかどうかを確認しました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予約されたキーワード

Javaスーパークラスから属性またはメソッドを参照したい場合はどうなりますか?この場合、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(); } } 

Using the reserved word super in Duke’s executeAction method  invokes the superclass method.  We then execute the specific action from Duke. That’s why we can see both messages in the output below:

 The Java Mascot is about to execute an action! Duke is going to punch! 

Take the polymorphism challenge!

Let’s try out what you’ve learned about polymorphism and inheritance. In this challenge, you’re given a handful of methods from Matt Groening’s The Simpsons, and your challenge is to deduce what the output for each class will be. To start, analyze the following code carefully:

 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(); } } 

What do you think? What will the final output be? Don’t use an IDE to figure this out! The point is to improve your code analysis skills, so try to determine the output for yourself.

Choose your answer and you’ll be able to find the correct answer below.

 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 

What just happened? Understanding polymorphism

For the following method invocation:

 new Lisa().talk("Sax :)"); 

the output will be “I love Sax!” This is  because we are passing a String to the method and Lisa has the method.

For the next invocation:

 Simpson simpson = new Bart("D'oh");

simpson.talk();

The output will be "Eat my shorts!" This is because we’re instantiating  the Simpson type with Bart.

Now check this one, which is a little trickier:

 Lisa lisa = new Lisa(); lisa.talk(); 

Here, we are using method overloading with inheritance. We are not passing anything to the talk method, which is why the Simpson talk method is invoked.  In this case the output will be:

 "Simpson!" 

Here’s one more:

 ((Bart) simpson).prank(); 

In this case, the prank String was passed when we instantiated the Bart class with new Bart("D'oh");. In this case,  first the super.prank method will be invoked, followed by the specific prank method from Bart. The output will be:

 "D'oh" "Knock Homer down" 

Video challenge! Debugging Java polymorphism and inheritance

Debugging is one of the easiest ways to fully absorb programming concepts while also improving your code. In this video you can follow along while I debug and explain the Java polymorphism challenge:

Common mistakes with polymorphism

It’s a common mistake to think it’s possible to invoke a specific method without using casting.

Another mistake is being unsure what method will be invoked when instantiating a class polymorphically. Remember that the method to be invoked is the method of the created instance.

Also remember that method overriding is not method overloading.

It’s impossible to override a method if the parameters are different. It is possible to change the return type of the overridden method if the return type is a subclass of the superclass method.

ポリモーフィズムについて覚えておくべきこと

  • 作成されたインスタンスは、ポリモーフィズムを使用するときに呼び出されるメソッドを決定します。
  • @Override注釈は、オーバーライドされたメソッドを使用するプログラマを義務付けます。そうでない場合は、コンパイラエラーが発生します。
  • ポリモーフィズムは、通常のクラス、抽象クラス、およびインターフェイスで使用できます。
  • ほとんどのデザインパターンは、何らかの形のポリモーフィズムに依存しています。
  • 多型サブクラスで特定のメソッドを使用する唯一の方法は、キャストを使用することです。
  • ポリモーフィズムを使用して、コードに強力な構造を設計することができます。
  • テストを実行します。これを行うと、この強力な概念を習得することができます!

解答

このJavaチャレンジャーへの答えはDです。出力は次のようになります。

 I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down 

このストーリー「Javaのポリモーフィズムと継承」は、もともとJavaWorldによって公開されました。