ポリモーフィズム

同じインターフェースを使って異なる実装を呼び出す仕組み

ポリモーフィズムとは

ポリモーフィズム(多態性)とは、「多くの形態を持つ」という意味で、プログラミングにおいては同じインターフェースを通じて異なる型のオブジェクトを操作できる能力を指します。Javaではポリモーフィズムは主に継承とインターフェースを通じて実現されます。

クラス図

図1: ポリモーフィズムのクラス図

ポリモーフィズムの基本形態

1. サブタイプポリモーフィズム(継承によるポリモーフィズム)

親クラスの参照変数が子クラスのオブジェクトを参照できる性質を利用します。

継承によるポリモーフィズムの例

// 親クラス
public class Animal {
    public void makeSound() {
        System.out.println("動物が鳴く");
    }
}

// 子クラス
public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("ワンワン");
    }
    
    public void fetch() {
        System.out.println("お手!");
    }
}

// 子クラス
public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("ニャーニャー");
    }
    
    public void scratch() {
        System.out.println("引っかく");
    }
}

// メイン処理
public class Main {
    public static void main(String[] args) {
        // ポリモーフィズムを使用
        Animal myDog = new Dog();  // 親クラスの変数で子クラスのオブジェクトを参照
        Animal myCat = new Cat();
        
        // 同じメソッドを呼び出すが、実際のオブジェクト型に応じた振る舞いをする
        myDog.makeSound();  // 出力: ワンワン
        myCat.makeSound();  // 出力: ニャーニャー
        
        // 注意: 親クラスの参照では子クラス固有のメソッドは直接呼び出せない
        // myDog.fetch();  // コンパイルエラー
        
        // キャストすれば子クラス固有のメソッドも呼び出せる
        ((Dog) myDog).fetch();  // 出力: お手!
    }
}

2. インターフェースによるポリモーフィズム

異なるクラスが同じインターフェースを実装することで、統一的な操作が可能になります。

インターフェースによるポリモーフィズムの例

// インターフェース
public interface Shape {
    double calculateArea();
    void draw();
}

// 実装クラス
public class Circle implements Shape {
    private double radius;
    
    public Circle(double radius) {
        this.radius = radius;
    }
    
    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
    
    @Override
    public void draw() {
        System.out.println("円を描画します");
    }
}

// 別の実装クラス
public class Rectangle implements Shape {
    private double width;
    private double height;
    
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
    
    @Override
    public double calculateArea() {
        return width * height;
    }
    
    @Override
    public void draw() {
        System.out.println("四角形を描画します");
    }
}

// 使用例
public class ShapeDemo {
    public static void main(String[] args) {
        Shape circle = new Circle(5.0);
        Shape rectangle = new Rectangle(4.0, 6.0);
        
        // インターフェースを通じて異なる実装を呼び出す
        System.out.println("円の面積: " + circle.calculateArea());
        System.out.println("四角形の面積: " + rectangle.calculateArea());
        
        circle.draw();
        rectangle.draw();
        
        // 図形の配列を使った処理
        Shape[] shapes = {
            new Circle(3.0),
            new Rectangle(2.0, 5.0),
            new Circle(7.0)
        };
        
        for (Shape shape : shapes) {
            System.out.println("面積: " + shape.calculateArea());
            shape.draw();
        }
    }
}

シーケンス図

図2: ポリモーフィズムの動作シーケンス

動的バインディング

動的バインディング(Dynamic Binding)とは、実行時に呼び出されるメソッドが決定される仕組みです。Javaでは、オーバーライドされたメソッドが呼び出されるとき、参照変数の型ではなく、実際のオブジェクトの型に基づいてメソッドが選択されます。

これにより、コンパイル時(静的)にはどのメソッドが呼び出されるか分からない場合でも、実行時(動的)に適切なメソッドが選択されます。

instanceofキーワードとキャスト

オブジェクトの実際の型を確認する必要がある場合、instanceofキーワードを使用できます。その後、適切にキャストすることで、そのオブジェクト型固有のメソッドにアクセスできます。

instanceofとキャストの例

public void processAnimal(Animal animal) {
    animal.makeSound();  // ポリモーフィズムを活用
    
    // 型によって追加の処理を行う
    if (animal instanceof Dog) {
        Dog dog = (Dog) animal;  // 明示的なキャスト
        dog.fetch();
    } else if (animal instanceof Cat) {
        Cat cat = (Cat) animal;
        cat.scratch();
    }
}

Java 16以降では、パターンマッチングを使った以下の書き方も可能です:

public void processAnimal(Animal animal) {
    animal.makeSound();
    
    // パターンマッチングによる条件分岐とキャスト
    if (animal instanceof Dog dog) {
        dog.fetch();  // キャスト不要
    } else if (animal instanceof Cat cat) {
        cat.scratch();
    }
}

ポリモーフィズムの利点

  • 柔軟性の向上: コードの変更なしに新しいクラスを追加できる
  • 拡張性: 既存のコードに影響を与えずに機能を拡張できる
  • 保守性: 共通インターフェースに対するコードの一元管理ができる
  • 再利用性: 汎用的なコードを書くことができる

実践例: ストラテジーパターン

ポリモーフィズムを活用したデザインパターンの代表例として、ストラテジーパターンがあります。このパターンでは、アルゴリズムをインターフェースで抽象化し、実行時に様々な実装を切り替えることができます。

ストラテジーパターンの例

// ストラテジーインターフェース
public interface PaymentStrategy {
    void pay(int amount);
}

// コンクリートストラテジー
public class CreditCardStrategy implements PaymentStrategy {
    private String name;
    private String cardNumber;
    private String cvv;
    private String dateOfExpiry;
    
    public CreditCardStrategy(String name, String cardNumber, String cvv, String dateOfExpiry) {
        this.name = name;
        this.cardNumber = cardNumber;
        this.cvv = cvv;
        this.dateOfExpiry = dateOfExpiry;
    }
    
    @Override
    public void pay(int amount) {
        System.out.println(amount + "円をクレジットカードで支払いました");
    }
}

// 別のコンクリートストラテジー
public class PaypalStrategy implements PaymentStrategy {
    private String emailId;
    private String password;
    
    public PaypalStrategy(String emailId, String password) {
        this.emailId = emailId;
        this.password = password;
    }
    
    @Override
    public void pay(int amount) {
        System.out.println(amount + "円をPaypalで支払いました");
    }
}

// コンテキスト
public class ShoppingCart {
    private PaymentStrategy paymentStrategy;
    
    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }
    
    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

// 使用例
public class PaymentDemo {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        
        // クレジットカード支払いを選択
        cart.setPaymentStrategy(new CreditCardStrategy("山田太郎", "1234 5678 9012 3456", "123", "12/25"));
        cart.checkout(12000);
        
        // Paypal支払いを選択
        cart.setPaymentStrategy(new PaypalStrategy("yamada@example.com", "password"));
        cart.checkout(5000);
    }
}

まとめ

ポリモーフィズムは、オブジェクト指向プログラミングの強力な機能であり、柔軟で拡張性の高いコードを書くための鍵となります。適切に活用することで、保守性が高く、変更に強いコードを設計できます。特に、インターフェースを活用したポリモーフィズムは、デザインパターンの多くの基盤となっており、実践的なJavaプログラミングでは必須のスキルです。