記事内にはプロモーションが含まれています

【Java】多重継承は禁止?インターフェースでの実現方法とダイヤモンド問題

【Java】多重継承は禁止?インターフェースでの実現方法とダイヤモンド問題 Java

Javaを学習していると、「クラスの継承は1つしかできない(単一継承)」というルールに必ず直面します。

C++やPythonなどの言語では複数のクラスを継承する「多重継承」が可能ですが、なぜJavaでは禁止されているのでしょうか。

「複数の親クラスの機能を持ったクラスを作りたい」
「インターフェースなら複数実装できると聞いたけれど、何が違うの?」

このような疑問を持つ方のために、この記事ではJavaがクラスの多重継承を禁止している理由である「ダイヤモンド問題」の解説から、インターフェースを使った多重継承(多重実装)の具体的なコーディング方法、そしてJava 8以降で導入された「デフォルトメソッド」における注意点までを徹底的に解説します。

Javaでクラスの多重継承が禁止されている理由

Javaの設計思想において、クラスの多重継承(extends A, B のような記述)は明確に禁止されています。

その最大の理由は、言語仕様をシンプルに保ち、「ダイヤモンド問題」と呼ばれる致命的な衝突を避けるためです。

メソッドの衝突「ダイヤモンド問題」とは

もしJavaでクラスの多重継承が許可されていたとしたら、どのような問題が起きるのでしょうか。

例えば、以下のような継承関係を想像してみてください。

  1. クラスA(親):「挨拶する」メソッドを持っている。
  2. クラスB(Aの子):「挨拶する」を「こんにちは」とオーバーライド。
  3. クラスC(Aの子):「挨拶する」を「こんばんは」とオーバーライド。
  4. クラスD(BとCの両方を継承した子)

このとき、クラスDのインスタンスで「挨拶する」メソッドを呼び出した場合、「Bの『こんにちは』」と「Cの『こんばんは』」のどちらを実行すれば良いのか、コンピュータは判断できなくなってしまいます。

継承図を描くと菱形(ダイヤモンド)の形状になることから、これを「ダイヤモンド問題」と呼びます。

Javaはこの複雑さを回避するために、クラスの継承を1つに限定しています。

インターフェースを使った多重継承(多重実装)の方法

クラスの多重継承はできませんが、Javaでは「インターフェース」を複数実装(implements)することは許可されています。

インターフェースは「仕様(メソッドの型)」のみを定義し、実体(中身のコード)を持たないことが基本だったため、メソッドが衝突しても問題にならなかったからです(どちらの実装を使うか悩む必要がなく、実装するクラス側で定義するため)。

複数のインターフェースを実装するサンプルコード

それでは、実際に2つのインターフェースを1つのクラスに実装する例を見てみましょう。

// 1つ目のインターフェース
interface Camera {
    void takePicture();
}

// 2つ目のインターフェース
interface MusicPlayer {
    void playMusic();
}

// 2つのインターフェースを実装したスマートフォンクラス
// カンマ区切りで複数のインターフェースを指定できる
class SmartPhone implements Camera, MusicPlayer {
    
    @Override
    public void takePicture() {
        System.out.println("写真を撮影しました。");
    }

    @Override
    public void playMusic() {
        System.out.println("音楽を再生します。");
    }
}

public class Main {
    public static void main(String[] args) {
        SmartPhone myPhone = new SmartPhone();
        
        myPhone.takePicture();
        myPhone.playMusic();
    }
}

実行結果

写真を撮影しました。
音楽を再生します。

class SmartPhone implements Camera, MusicPlayer のように、implements キーワードの後ろにカンマ区切りでインターフェースを並べることで、複数の機能を持ったクラスを定義できます。

SmartPhone クラスは、Camera としての機能(takePicture)と MusicPlayer としての機能(playMusic)の両方を強制的に実装させられます。

これにより、擬似的な多重継承のような振る舞いを安全に実現しています。

Java 8以降のデフォルトメソッドと競合の解決

Java 8からはインターフェースに「デフォルトメソッド(default method)」という機能が追加されました。

これにより、インターフェース自体にメソッドの実装(中身)を持てるようになりましたが、同時に「ダイヤモンド問題」に似たメソッドの競合が発生する可能性も生まれました。

デフォルトメソッドが競合した場合のルール

同じ名前・同じ引数のデフォルトメソッドを持つ2つのインターフェースを実装した場合、コンパイルエラーになります。

この場合、実装するクラス側で「どちらのメソッドを使うか」を明示的にオーバーライドして指定する必要があります。

interface InterfaceA {
    default void hello() {
        System.out.println("InterfaceAのこんにちは");
    }
}

interface InterfaceB {
    default void hello() {
        System.out.println("InterfaceBのこんにちは");
    }
}

// 両方に hello() があるため、そのままだとコンパイルエラーになる
class MyClass implements InterfaceA, InterfaceB {
    
    @Override
    public void hello() {
        // どちらを使うか明示的に記述する
        // InterfaceA.super.メソッド名() で指定可能
        InterfaceA.super.hello();
        
        // もちろん、独自の処理を書いてもOK
        System.out.println("MyClassで解決しました");
    }
}

public class DefaultMethodMain {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.hello();
    }
}

実行結果

InterfaceAのこんにちは
MyClassで解決しました

InterfaceAInterfaceB の両方に default void hello() が定義されています。

MyClass がこれらを同時に実装しようとすると、コンパイラはどちらの hello を使えばよいかわからないためエラーを出します。

そこで、MyClass 内で hello メソッドをオーバーライドし、InterfaceA.super.hello() のように記述して「Aの実装を使う」と明示するか、あるいは全く新しい処理を記述することで解決します。

クラス継承とインターフェース実装の優先順位

もし、親クラスとインターフェースの両方に同じメソッドがあった場合はどうなるのでしょうか?

Javaには「クラス優先の法則(Class Wins)」というルールがあります。

クラスの実装が常に勝つ

class SuperClass {
    public void method() {
        System.out.println("親クラスのメソッド");
    }
}

interface MyInterface {
    default void method() {
        System.out.println("インターフェースのデフォルトメソッド");
    }
}

// 親クラスを継承し、インターフェースも実装
class ChildClass extends SuperClass implements MyInterface {
    // 何も書かなくてもエラーにならない
}

public class ClassWinsMain {
    public static void main(String[] args) {
        ChildClass obj = new ChildClass();
        obj.method();
    }
}

実行結果

親クラスのメソッド

SuperClassMyInterface の両方に method() がありますが、この場合はコンパイルエラーにはならず、自動的に親クラス(SuperClass)のメソッドが優先して使用されます。

クラスの継承関係による振る舞いの保証を、インターフェースの拡張(デフォルトメソッド)よりも重要視しているためです。

このルールのおかげで、既存のクラス設計を壊さずにインターフェースを進化させることが可能になっています。

Javaのスキルを活かして年収を上げる方法

以上、Javaでクラスの多重継承が禁止されている理由や、多重継承を実現する方法などについて解説してきました。

なお、Javaのスキルがある場合には、「転職して年収をアップさせる」「副業で稼ぐ」といった方法を検討するのがおすすめです。

Javaエンジニアの需要は非常に高いため、転職によって数十万円の年収アップはザラで、100万円以上年収が上がることも珍しくありません。

なお、転職によって年収を上げたい場合は、エンジニア専門の転職エージェントサービスを利用するのが最適です。

今すぐ転職する気がなくとも、とりあえず転職エージェントに無料登録しておくだけで、スカウトが届いたり、思わぬ好待遇の求人情報が送られてきたりするというメリットがあります。

併せて、副業案件を獲得できるエージェントにも登録しておくと、空いている時間を活かして稼げるようなJavaの案件を探しやすくなります。

転職エージェントも副業エージェントも、登録・利用は完全無料なので、どんな求人や副業案件があるのか気になる方は、気軽に利用してみるとよいでしょう。