Menu
Coddy logo textTech

なぜ多重継承ができないのか

CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 26/87。

Javaは、クラスが複数のクラスを継承することを意図的に防いでいます。この制限は、ダイヤモンド問題として知られる問題のために存在します。

Javaで多重継承が許可されていたと想像してみてください。次のような仮想のシナリオを考えてみましょう:

// このコードはJavaでは無効です
public class A {
    public void greet() {
        System.out.println("Hello from A");
    }
}

public class B extends A {
    @Override
    public void greet() {
        System.out.println("Hello from B");
    }
}

public class C extends A {
    @Override
    public void greet() {
        System.out.println("Hello from C");
    }
}

// Javaでは許可されていません!
public class D extends B, C { }

D オブジェクトに対して greet() を呼び出した場合、どちらのバージョンが実行されるべきでしょうか?B のものでしょうか、それとも C のものでしょうか?この曖昧さは、継承図が描く菱形(ダイヤモンド型)にちなんで「ダイヤモンド問題(Diamond Problem)」と呼ばれています。

Javaの解決策は単純です。クラスの単一継承のみを許可することです。しかし、Javaは代替案を提供しています。クラスは複数のインターフェースを実装することができ、これについては今後の章で学習します。

インターフェースは、ダイヤモンド問題を異なる方法で解決し、曖昧さを排除しつつ柔軟性を提供します。

// これはJavaで有効です
public class Dog extends Animal implements Runnable, Comparable<Dog> {
    // 複数のインターフェースを実装できます
}

この設計上の選択により、Javaの継承モデルはクリーンで予測可能な状態に保たれ、同時にインターフェースを通じた強力なコードの再利用が可能になります。

challenge icon

チャレンジ

簡単

Javaの単一継承ルールを実証し、この制約の中でどのように柔軟な設計を実現できるかを示す音楽プレーヤーシステムを構築しましょう。

音楽プレーヤーと音声アシスタントの両方の機能を組み合わせたスマートスピーカーを作成したいと想像してください。Javaでは複数のクラスを継承(多重継承)することは許可されていないため、単一継承を使用して慎重に階層を設計する必要があります。

コードを整理するために4つのファイルを作成します:

  • AudioDevice.java: すべてのオーディオ再生デバイスが共有するベースクラスを作成します。以下の内容を含める必要があります:
    • deviceName (String) のプライベートフィールド
    • デバイス名を受け取るコンストラクタ
    • 名前を返す getDeviceName() メソッド
    • [deviceName]: Playing audio と出力する playSound() メソッド
  • MusicPlayer.java: AudioDeviceを継承し、音楽固有の機能を追加するクラスを作成します:
    • currentSong (String) のプライベートフィールド
    • デバイス名を受け取り、super(deviceName) を使用するコンストラクタ
    • 現在の曲を設定する setSong(String song) メソッド
    • playSound() をオーバーライドして、[deviceName]: Now playing - [currentSong] と出力するようにします
  • SmartSpeaker.java: MusicPlayerを継承するクラスを作成します(MusicPlayerとVoiceAssistantクラスの両方ではありません。それは多重継承になってしまいます!)。スマートスピーカーは、音声機能を追加することで音楽プレーヤーを拡張します:
    • assistantName (String) のプライベートフィールド
    • デバイス名とアシスタント名を受け取り、super(deviceName) を使用するコンストラクタ
    • [assistantName]: Processing "[command]" と出力する voiceCommand(String command) メソッド
    • [deviceName] with [assistantName] assistant と出力する getFullInfo() メソッド
  • Main.java: 多段階継承チェーンを実証します。デバイス名 (String)、アシスタント名 (String)、曲名 (String) の3つの入力を受け取ります。SmartSpeakerを作成し、曲を設定してから、次のメソッドを順番に呼び出します:
    • getFullInfo()
    • playSound()
    • voiceCommand("skip to next")

デバイス名、アシスタント名、曲名の3つの入力を受け取ります。

SmartSpeakerが、複数の無関係なクラスを継承しようとするのではなく、チェーン(AudioDevice -> MusicPlayer -> SmartSpeaker)の中でMusicPlayerを継承することで、どのように音楽機能を取得しているかに注目してください。これが、継承をクリーンで曖昧さのないものに保つためのJavaのアプローチです!

チートシート

Javaは単一継承のみを許可しています。つまり、クラスは1つの他のクラスのみを拡張(継承)できます。これにより、クラスが複数の親クラスから同じメソッドを継承できる場合に発生する曖昧さ、いわゆる菱形継承問題 (Diamond Problem)を防いでいます。

// Javaでは許可されていません - 多重継承
public class D extends B, C { } // エラー!

しかし、Javaでは代替案として、クラスが複数のインターフェースを実装することを許可しています。

// Javaで有効
public class Dog extends Animal implements Runnable, Comparable<Dog> {
    // 1つのクラスを継承し、複数のインターフェースを実装できます
}

Javaは多段階継承(継承チェーンの作成)をサポートしています。

public class AudioDevice { }

public class MusicPlayer extends AudioDevice { }

public class SmartSpeaker extends MusicPlayer { }
// SmartSpeakerはMusicPlayerとAudioDeviceの両方から継承します

自分で試してみよう

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 入力を読み込む
        String deviceName = scanner.nextLine();
        String assistantName = scanner.nextLine();
        String songName = scanner.nextLine();
        
        // TODO: deviceName と assistantName を使用して SmartSpeaker を作成する
        
        // TODO: setSong() を使用して曲を設定する
        
        // TODO: getFullInfo() を呼び出す
        
        // TODO: playSound() を呼び出す
        
        // TODO: voiceCommand("skip to next") を呼び出す
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン