1. コレクションにおけるポリモーフィズム:なぜ必要か?
まず問いから始めましょう。「実際のプログラムでなぜポリモーフィズムが必要なのか?」
動物園を想像してください。基底クラス Animal があり、そこから多くのサブクラスが継承されています。たとえば Dog、Cat、Cow、Parrot、さらには Platypus(カモノハシ。珍しいもの好き向け)まで。どれも音を出すこと(makeSound())ができますが、そのやり方はそれぞれ異なります。
各動物ごとに別々の配列を作るのではなく、Animal 型の配列やリストを宣言して、誰でもそこに入れます:
Animal[] animals = {
new Dog(),
new Cat(),
new Cow(),
new Parrot()
};
この配列を走査して、各要素に対して makeSound() を呼び出せます:
for (Animal animal : animals) {
animal.makeSound();
}
まるで魔法です! 各オブジェクトは自分がどんな音を出すべきかを知っており、if や switch を大量に書く必要はありません。
たとえ話
動物の群れに「声!」と号令をかけるようなものです。犬は吠え、猫はニャーと鳴き、牛はモーと鳴きます。誰が誰かを特定する必要はなく、同じメソッドを呼ぶだけです。
2. 実用例:従業員の階層
より実生活(そして将来のITの仕事)に近い例にしてみましょう。会社にさまざまな従業員がいるとします。マネージャー、開発者、テスター。それぞれに work() メソッドがありますが、動作は異なります。
基底クラスの宣言
public class Employee {
public void work() {
System.out.println("従業員が働いています...");
}
}
サブクラス
public class Manager extends Employee {
@Override
public void work() {
System.out.println("マネージャーが会議を開いています。");
}
}
public class Developer extends Employee {
@Override
public void work() {
System.out.println("開発者がコードを書いています。");
}
}
public class Tester extends Employee {
@Override
public void work() {
System.out.println("テスターがバグを探しています。");
}
}
基底型の配列/リストを使う
public class CompanyDemo {
public static void main(String[] args) {
Employee[] team = {
new Manager(),
new Developer(),
new Tester(),
new Developer()
};
for (Employee e : team) {
e.work(); // 各オブジェクトに対して「正しい」バージョンのメソッドが呼ばれます
}
}
}
実行結果:
マネージャーが会議を開いています。
開発者がコードを書いています。
テスターがバグを探しています。
開発者がコードを書いています。
利点は何か?
- 「もし Developer なら〜」といった分岐を大量に書かなくてよい。
- 新しい従業員(例:Designer)を追加するには、新しいクラスを作って配列に入れるだけ。
- 従業員配列を使う側のコードは一切変更不要!
3. ポリモーフィズムの利点:柔軟性と拡張性
会社に新しいタイプの従業員 — Designer — が現れたとしましょう。やることは新しいクラスを作るだけです:
public class Designer extends Employee {
@Override
public void work() {
System.out.println("デザイナーがモックアップを作成しています。");
}
}
これでチームにデザイナーを追加できます:
Employee[] team = {
new Manager(),
new Developer(),
new Tester(),
new Designer()
};
これで完了です! 配列を走査して work() を呼び出すコードに一行も手を入れずに、新しい従業員タイプを正しく扱えるようになります。
これこそが拡張性です。コードは新しい種類のオブジェクトに容易に適応できます。
4. ポリモーフィズムの制約:裏側
残念ながら、どんな「魔法」にも制約があります(RPG と同じくコストも伴います)。
利用できるのは基底クラスのメソッドだけ
Employee 型の変数で作業する場合、呼び出せるのは Employee に宣言されているメソッドだけです。もし Developer に特別なメソッド writeCode() があっても、直接呼ぶことはできません:
Employee e = new Developer();
// e.writeCode(); // コンパイルエラー: Employee にはそのようなメソッドはありません!
どうしても特化メソッドを呼びたい場合は、型キャストが必要です。ただし、これは最後の手段です。頻繁にキャストしているなら、クラス設計を見直すべきかもしれません。— 基底クラスやインターフェースに必要なメソッドを含めるべきです。
if (e instanceof Developer) {
Developer dev = (Developer) e;
dev.writeCode();
}
しかし、そうすると本来の汎用性や優雅さが失われます。そのため、基底クラスには本当にすべてのサブクラスに必要なメソッドだけを用意するように設計しましょう。
5. 実践:従業員の階層を実装する
良い練習として、いくつかの従業員タイプがいる簡単なアプリを作り、ポリモーフィズムで処理してみましょう。
ステップ1:基底クラスとサブクラス
// Employee.java
public class Employee {
public void work() {
System.out.println("従業員が働いています...");
}
}
// Manager.java
public class Manager extends Employee {
@Override
public void work() {
System.out.println("マネージャーが会議を開いています。");
}
}
// Developer.java
public class Developer extends Employee {
@Override
public void work() {
System.out.println("開発者がコードを書いています。");
}
}
// Tester.java
public class Tester extends Employee {
@Override
public void work() {
System.out.println("テスターがバグを探しています。");
}
}
ステップ2:メインクラス
// CompanyDemo.java
public class CompanyDemo {
public static void main(String[] args) {
Employee[] team = {
new Manager(),
new Developer(),
new Tester(),
new Developer()
};
for (Employee e : team) {
e.work();
}
}
}
ステップ3:拡張性を追加する
1か月後に新しい従業員 — デザイナー — が入社したとしましょう。やることは次のとおりです:
public class Designer extends Employee {
@Override
public void work() {
System.out.println("デザイナーがモックアップを作成しています。");
}
}
これでチームにデザイナーを追加できます:
Employee[] team = {
new Manager(),
new Developer(),
new Tester(),
new Designer()
};
まとめ
主要なコード(CompanyDemo)は一切変更していません! これがポリモーフィズムの力です。
6. ポリモーフィズム利用時のよくあるミス
ミス1: 基底型の参照から特化メソッドを呼べると期待してしまう。
初心者はしばしば、スーパークラス型の変数からサブクラス特有のメソッドを呼ぼうとします。例:
Employee e = new Developer();
// e.writeCode(); // エラー!そのようなメソッドは Employee には定義されていません。
特化メソッドを呼ぶにはキャストが必要ですが、それでは汎用性が失われます。
ミス2: アノテーション @Override を付け忘れる。
アノテーションを付け忘れると、誤って新しいメソッドを書いてしまう(例えば名前を間違える)可能性があります。その場合、ポリモーフィズムが機能せず、スーパークラス版が呼ばれてしまいます。
ミス3: 共通のインターフェースがない。
基底クラスに必要なメソッドが含まれていなければ、ポリモーフィズムは成立しません。例えば Employee に work() がなければ、従業員配列をループしてそのメソッドを全員に対して呼ぶことはできません。
ミス4: オープン/クローズドの原則に反する。
新しい従業員タイプを追加するために配列/リストを走査するコードを変更しなければならないのなら、ポリモーフィズムを正しく使えていません。
GO TO FULL VERSION