web-dev-qa-db-ja.com

なぜJavaは多重継承を許可しないが、デフォルトの実装で複数のインターフェースに準拠することを許可するのか

私はこれを求めていません-> なぜJavaには多重継承がないのに、複数のインターフェースを実装することが許可されているのですか?

Javaでは、多重継承は許可されていませんが、Java 8の後に、インターフェースはdefaultメソッドを持つことができます(メソッド自体を実装できます) )、抽象クラスのように。このコンテキスト内では、多重継承も許可する必要があります。

interface TestInterface 
{ 
    // abstract method 
    public void square(int a); 

    // default method 
    default void show() 
    { 
      System.out.println("Default Method Executed"); 
    } 
} 
47
Asanka

物事はそれほど単純ではありません。
クラスが、同じシグネチャを持つデフォルトのメソッドを定義する複数のインターフェースを実装している場合、コンパイラは、クラスに対してこのメ​​ソッドをオーバーライドすることを強制します。

たとえば、次の2つのインターフェイスの場合:

public interface Foo {
    default void doThat() {
        // ...
    }
}

public interface Bar {    
    default void doThat() {
        // ...
    }       
}

コンパイルしません:

public class FooBar implements Foo, Bar{
}

メソッドを定義/オーバーライドして、あいまいさを解消する必要があります。
たとえば、次のようなBar実装に委任できます。

public class FooBar implements Foo, Bar{    
    @Override
    public void doThat() {
        Bar.super.doThat();
    }    
}

または、次のようなFoo実装に委任します。

public class FooBar implements Foo, Bar {
    @Override
    public void doThat() {
        Foo.super.doThat();
    }
}

または、まだ別の動作を定義します:

public class FooBar implements Foo, Bar {
    @Override
    public void doThat() {
        // ... 
    }
}

この制約は、Javaがインターフェースのデフォルトメソッドであっても多重継承を許可しないことを示しています。


私は、主なものである複数の問題が発生する可能性があるため、複数の継承に同じロジックを適用できないと思います:

  • 両方の継承クラスのメソッドのあいまいさをオーバーライド/削除すると、このメソッドに内部的に依存している場合、副作用が発生し、継承クラスの全体的な動作が変更される可能性があります。デフォルトのインターフェースでは、このリスクも回避されますが、デフォルトのメソッドは、クラス内での複数の内部呼び出しなどの複雑な処理を導入したり、ステートフルになるように設計されていないため(実際には、インターフェースはインスタンスフィールドをホストできません)、それほどまれではありません。
  • 複数のフィールドを継承する方法は?また、言語で許可されていても、以前に引用したこの問題とまったく同じ問題が発生します:継承クラスの動作における副作用:サブクラス化するAクラスとBクラスで定義されたint fooフィールドは同じではありません意味と意図。
36
davidxxx

言語設計者はすでにそれについて考えていたので、これらのことはコンパイラーによって強制されます。だからあなたが定義する場合:

interface First {
    default void go() {
    }
}

interface Second {
    default void go() {
    }
}

そして、両方のインターフェースにクラスを実装します:

static class Impl implements First, Second {

}

コンパイルエラーが発生します。 goをオーバーライドして、周囲にあいまいさを作成しないようにする必要があります。

しかし、次のようにすることで、ここでコンパイラをだますことができると考えるかもしれません。

interface First {
    public default void go() {
    }
}

static abstract class Second {
    abstract void go();
}

static class Impl extends Second implements First {
}

First::goはすでにSecond::goの実装を提供していると考えることができます。これはあまりにも注意が必要なので、これもコンパイルされません。

JLS 9.4.1.3:同様に、署名が一致する抽象メソッドとデフォルトメソッドが継承されると、エラーが発生します。この場合、どちらかを優先することができます-おそらく、デフォルトメソッドも抽象メソッドの合理的な実装を提供すると仮定します。偶然の名前と署名以外に、デフォルトメソッドが抽象メソッドのコントラクトと一貫して動作することを信じる理由がないため、これは危険です-最初に開発されました。この状況では、デフォルトの実装が適切であることを積極的にアサートするようにユーザーに要求する方が安全です(オーバーライド宣言を介して)。

Javaに新たに追加された場合でも多重継承が許可されないことを固めるために、私が持ち込む最後のポイントは、インターフェースからの静的メソッドが継承されないことです。静的メソッドはデフォルトで継承されます

static class Bug {
    static void printIt() {
        System.out.println("Bug...");
    }
}

static class Spectre extends Bug {
    static void test() {
        printIt(); // this will work just fine
    }
}

ただし、インターフェイスのそれを変更した場合(クラスとは異なり、複数のインターフェイスを実装できます):

interface Bug {
    static void printIt() {
        System.out.println("Bug...");
    }
}

static class Spectre implements Bug {
    static void test() {
        printIt(); // this will not compile
    }
}

現在、これはコンパイラおよびJLSでも禁止されています。

JLS 8.4.8:クラスは、そのスーパーインターフェースから静的メソッドを継承しません。

25
Eugene

Javaはフィールドの多重継承を許可しません。 JVMでのサポートは困難です。これは、ヘッダーがあるオブジェクトの先頭への参照のみを持つことができ、任意のメモリ位置ではないためです。

Oracle/Openjdkでは、オブジェクトにはヘッダーがあり、次にスーパークラスのフィールド、次にスーパークラスのフィールドなどが続きます。クラスのフィールドをヘッダーに対して異なるオフセットで表示できるようにすることは重要な変更です異なるサブクラスのオブジェクトの。ほとんどの場合、オブジェクト参照は、これをサポートするためにオブジェクトヘッダーへの参照およびフィールドへの参照になる必要があります。

13
Peter Lawrey

それは主に「ダイヤモンドの問題」に関連しています。現在、同じメソッドで複数のインターフェースを実装する場合、コンパイラは、使用するメソッドがわからないため、実装するメソッドをオーバーライドするように強制します。 Java作成者は、インターフェイスがデフォルトのメソッドを使用できなかったときに、この問題を取り消したかったと思います。今、彼らはアイデアを思い付きました。それは、ストリーム/ラムダ式で機能的なインターフェイスとしてそれらを使用し、処理でデフォルトのメソッドを利用できるため、インターフェイスで実装されたメソッドを持つことができるのは良いことです。クラスでそれを行うことはできませんが、ダイヤモンドの問題はまだそこにあります。それは私の推測です:)

4
Mershel

多重継承の主な問題は、順序付け(superのオーバーライドと呼び出し)、フィールド、およびコンストラクターです。インターフェイスにはフィールドやコンストラクタがないため、問題は発生しません。

他の言語を見ると、それらは通常2つの広いカテゴリに分類されます。

  1. 多重継承と特殊なケースを明確にするいくつかの機能を備えた言語:仮想継承[C++]、最も派生したクラスのすべてのスーパーコンストラクターへの直接呼び出し[C++]、スーパークラスの線形化[Python]、super[Python]など.

  2. 異なる概念を持つ言語、通常interfacestraitsmixinsmodulesなど。 Java]またはパラメーターを持つコンストラクター[ごく最近までのスカラ]、可変フィールド[Java]、オーバーライドの特定のルール(たとえば、ミックスインは基本クラス[Ruby]よりも優先されるため、多数のユーティリティメソッドが必要な場合に含めることができます)など。Javaはこれらのような言語になりました。

フィールドとコンストラクタを禁止するだけで、多重継承に関連する多くの問題を解決できるのはなぜですか?

  • 重複した基本クラスに重複したフィールドを含めることはできません。
    • メインのクラス階層は依然として線形です。
  • ベースオブジェクトを間違った方法で構築することはできません。
    • Objectにpublic/protectedフィールドがあり、すべてのサブクラスにこれらのフィールドを設定するコンストラクターがあると想像してください。複数のクラス(すべてオブジェクトから派生)から継承する場合、どのクラスがフィールドを設定しますか?最後のクラス?彼らは階層の兄弟になるので、お互いについて何も知りません。これを回避するには、オブジェクトのコピーを複数持つ必要がありますか?すべてのクラスは正しく相互運用できますか?
  • Javaのフィールドは仮想(オーバーライド可能)ではなく、単なるデータストレージであることに注意してください。
    • フィールドがメソッドのように動作し、オーバーライドできる言語を作成できます(実際のストレージは常にプライベートになります)が、それははるかに大きな変更であり、おそらくJavaと呼ばれることはないでしょう。
  • インターフェイスはそれ自体ではインスタンス化できません。
    • 常に具象クラスと組み合わせてください。これにより、コンストラクターが不要になり、プログラマーの意図も明確になります(つまり、具体的なクラスとは何か、アクセサリーインターフェイス/ミックスインとは何か)。これは、すべてのあいまいさを解決する明確な場所、具体的なクラスも提供します。
4
marcus

インターフェースのdefaultメソッドには、次の問題があります。

実装されたインターフェースの両方が同じメソッドシグネチャでデフォルトメソッドを定義する場合、実装クラスは使用するデフォルトメソッドを認識しません。

実装クラスは、使用するデフォルトメソッドを明示的に指定するか、独自のメソッドを定義する必要があります。

したがって、Java-8のdefaultメソッドは多重継承を促進しません。デフォルトメソッドの背後にある主な動機は、ある時点で既存のインターフェースにメソッドを追加する必要がある場合、既存の実装クラスを変更せずにメソッドを追加できることです。このように、インターフェイスは古いバージョンと互換性があります。ただし、デフォルトメソッドを使用する動機を覚えておく必要があり、インターフェイスと実装の分離を維持する必要があります。

3
S.K.