8.多態性
学習目標
- 同じメソッド呼び出しが、変数の実体に応じて異なる動作をする仕組みを説明できる
- 多態性がインターフェイス実装と継承の両方で働くことを説明できる
Listに複数の実体をまとめて入れ、一括で処理できる- メソッドの引数を上位の型にして、複数の実体を受け取れる
1. 実体に応じた動作
Section titled “1. 実体に応じた動作”インターフェイス を実装した Circle は、Shape 型の変数に代入できます。
Shape shape = new Circle(5.0);System.out.println(shape.area()); // 78.53981633974483shape の型は Shape ですが、shape.area() を呼ぶと Circle の area() が実行されます。変数の型ではなく、変数に入っているインスタンスの実体に応じて、実行されるメソッドが決まります。この仕組みを 多態性(ポリモーフィズム)と呼びます。
多態性は、継承した子クラスでも同じように働きます。継承 の Engineer を Employee 型の変数に代入すると、employee.introduce() はオーバーライドした Engineer の introduce() を実行します。
Employee employee = new Engineer("田中", 300000, "Java");employee.introduce(); // 田中(基本給: 300000円, スキル: Java)インターフェイスを実装したクラスでも、継承した子クラスでも、上位の型で扱えば実体のメソッドが呼ばれます。標準ライブラリの List<Student> students = new ArrayList<>() も同じです。型は List インターフェイス、実体は ArrayList で、students.add(...) は実体である ArrayList の add の実装で実行されます。
2. コレクションでの一括処理
Section titled “2. コレクションでの一括処理”多態性が役立つのは、異なる実体を 1 つのコレクションにまとめて扱うときです。Circle も Rectangle も Shape を実装しているので、Shape 型の List にまとめて入れられます。
import java.util.ArrayList;import java.util.List;
List<Shape> shapes = new ArrayList<>();shapes.add(new Circle(5.0));shapes.add(new Rectangle(4.0, 6.0));shapes.add(new Circle(2.0));
double total = 0;for (Shape shape : shapes) { total += shape.area();}System.out.println("面積の合計: " + total);面積の合計: 115.09955657750091ループの中で shape の型は Shape ですが、各反復で shape.area() が呼ぶメソッドは、その要素の実体によって変わります。
| 反復 | shape の実体 | 実行される area() | 戻り値 |
|---|---|---|---|
| 1 回目 | Circle(5.0) | Circle の area() | 78.5398… |
| 2 回目 | Rectangle(4.0, 6.0) | Rectangle の area() | 24.0 |
| 3 回目 | Circle(2.0) | Circle の area() | 12.5663… |
ループの本体(total += shape.area())は 1 つだけですが、実体に応じて異なる area() が実行されます。新しい図形(Triangle など)を Shape の実装として追加しても、shapes に入れるだけで合計に含まれ、このループは変更不要です。
2-1. 型で分岐するコードとの比較
Section titled “2-1. 型で分岐するコードとの比較”多態性を使わずに同じ処理を書こうとすると、要素の実体を 1 つずつ判定して分岐するコードになります。
double total = 0;for (Shape shape : shapes) { total += shape.area();}要素が Circle でも Rectangle でも、shape.area() の 1 行で面積を取得できます。各クラスが自分の area() を持つため、ループ側は実体の種類を意識しません。
double total = 0;for (Shape shape : shapes) { if (shape instanceof Circle circle) { total += circle.getRadius() * circle.getRadius() * Math.PI; } else if (shape instanceof Rectangle rectangle) { total += rectangle.getWidth() * rectangle.getHeight(); }}instanceof で実体の型を判定し、型ごとに面積の計算式を書いています。図形を追加するたびに else if を増やし、各図形のフィールドを読む getter も必要になります。
instanceof は変数の実体が指定した型かを判定する演算子です。型で分岐するコードは、図形が増えるたびに分岐を足す必要があります。多態性を使えば、計算式は各クラスの area() に閉じ込められ、ループ側は変更不要です。
3. 引数での多態性
Section titled “3. 引数での多態性”多態性は、メソッドの引数でも使えます。引数の型を上位の型にすると、その型の実体ならどれでも渡せます。
図形 1 つを受け取って、面積つきで説明を表示するメソッド printArea を考えます。引数の型を Shape にすると、Circle でも Rectangle でも渡せます。
public class ShapePrinter { public void printArea(Shape shape) { System.out.println("面積: " + shape.area()); }}ShapePrinter printer = new ShapePrinter();
printer.printArea(new Circle(5.0));printer.printArea(new Rectangle(4.0, 6.0));面積: 78.53981633974483面積: 24.0printArea の引数の型は Shape です。渡されたインスタンスの実体が Circle でも Rectangle でも、shape.area() は実体のメソッドを実行します。ShapePrinter は Circle や Rectangle という具体的なクラスを知りません。新しい図形を追加しても、printArea はそのまま受け取れます。
この形は、コンポジション や インターフェイス で書いた GradeReportService でも使っていました。コンストラクターの引数 GradeJudge judge は、StandardGradeJudge でも StrictGradeJudge でも受け取れます。利用側が渡す実体を変えるだけで GradeReportService の挙動が変わったのは、引数での多態性が働いていたからです。