Skip to content
Playground

8.多態性

学習目標

  • 同じメソッド呼び出しが、変数の実体に応じて異なる動作をする仕組みを説明できる
  • 多態性がインターフェイス実装と継承の両方で働くことを説明できる
  • List に複数の実体をまとめて入れ、一括で処理できる
  • メソッドの引数を上位の型にして、複数の実体を受け取れる

インターフェイス を実装した Circle は、Shape 型の変数に代入できます。

Main.java
Shape shape = new Circle(5.0);
System.out.println(shape.area()); // 78.53981633974483

shape の型は Shape ですが、shape.area() を呼ぶと Circlearea() が実行されます。変数の型ではなく、変数に入っているインスタンスの実体に応じて、実行されるメソッドが決まります。この仕組みを 多態性(ポリモーフィズム)と呼びます。

多態性は、継承した子クラスでも同じように働きます。継承EngineerEmployee 型の変数に代入すると、employee.introduce() はオーバーライドした Engineerintroduce() を実行します。

Main.java
Employee employee = new Engineer("田中", 300000, "Java");
employee.introduce(); // 田中(基本給: 300000円, スキル: Java)

インターフェイスを実装したクラスでも、継承した子クラスでも、上位の型で扱えば実体のメソッドが呼ばれます。標準ライブラリの List<Student> students = new ArrayList<>() も同じです。型は List インターフェイス、実体は ArrayList で、students.add(...) は実体である ArrayListadd の実装で実行されます。

多態性が役立つのは、異なる実体を 1 つのコレクションにまとめて扱うときです。CircleRectangleShape を実装しているので、Shape 型の List にまとめて入れられます。

Main.java
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)Circlearea()78.5398…
2 回目Rectangle(4.0, 6.0)Rectanglearea()24.0
3 回目Circle(2.0)Circlearea()12.5663…

ループの本体(total += shape.area())は 1 つだけですが、実体に応じて異なる area() が実行されます。新しい図形(Triangle など)を Shape の実装として追加しても、shapes に入れるだけで合計に含まれ、このループは変更不要です。

2-1. 型で分岐するコードとの比較

Section titled “2-1. 型で分岐するコードとの比較”

多態性を使わずに同じ処理を書こうとすると、要素の実体を 1 つずつ判定して分岐するコードになります。

Main.java
double total = 0;
for (Shape shape : shapes) {
total += shape.area();
}

要素が Circle でも Rectangle でも、shape.area() の 1 行で面積を取得できます。各クラスが自分の area() を持つため、ループ側は実体の種類を意識しません。

instanceof は変数の実体が指定した型かを判定する演算子です。型で分岐するコードは、図形が増えるたびに分岐を足す必要があります。多態性を使えば、計算式は各クラスの area() に閉じ込められ、ループ側は変更不要です。

多態性は、メソッドの引数でも使えます。引数の型を上位の型にすると、その型の実体ならどれでも渡せます。

図形 1 つを受け取って、面積つきで説明を表示するメソッド printArea を考えます。引数の型を Shape にすると、Circle でも Rectangle でも渡せます。

ShapePrinter.java
public class ShapePrinter {
public void printArea(Shape shape) {
System.out.println("面積: " + shape.area());
}
}
Main.java
ShapePrinter printer = new ShapePrinter();
printer.printArea(new Circle(5.0));
printer.printArea(new Rectangle(4.0, 6.0));
面積: 78.53981633974483
面積: 24.0

printArea の引数の型は Shape です。渡されたインスタンスの実体が Circle でも Rectangle でも、shape.area() は実体のメソッドを実行します。ShapePrinterCircleRectangle という具体的なクラスを知りません。新しい図形を追加しても、printArea はそのまま受け取れます。

この形は、コンポジションインターフェイス で書いた GradeReportService でも使っていました。コンストラクターの引数 GradeJudge judge は、StandardGradeJudge でも StrictGradeJudge でも受け取れます。利用側が渡す実体を変えるだけで GradeReportService の挙動が変わったのは、引数での多態性が働いていたからです。