Javaに複数の継承がないのに、複数のインターフェースの実装が許可されているのはなぜですか?


153

Javaでは多重継承は許可されていませんが、複数のインターフェースを実装できます。どうして?


1
わかりやすいように質問のタイトルを編集しました。
Bozho

4
興味深いことに、JDK 8には、インターフェースメソッドの実装の定義を可能にする拡張メソッドがあります。ルールは動作の複数の継承を管理するように定義されていますが、状態の継承は管理していません(私が理解している方が問題が多いと思います。)
Edwin Dalorzo 2012

1
あなたが「Javaが多重継承を実現する方法」だけを伝える回答に時間を浪費する前に...内部的に何が起こっている必要があるかを論理的に示す@Prabal Srivastavaの回答に行くことをお勧めします。インターフェースに、多重継承を許可する権利のみを許可します。
Yo Apps

回答:


228

インタフェースのみを指定しているのでクラスがない、やっているどのようにそれをやっています。

多重継承の問題は、2つのクラスが同じことを行う異なる方法を定義する可能性があり、サブクラスがどちらを選択するかを選択できないことです。


8
私はC ++を使用していて、まったく同じ問題に何度も遭遇しました。私は最近、Scalaが「C ++」の方法と「Java」の方法の中間にあるような「特性」を持っていることを読みました。
Niels Basjes、2010

2
このようにして、「ダイヤモンドの問題」を回避します。en.wikipedia.org
Nick L.

6
Java 8以降、2つの同一のデフォルトメソッドを定義できます(各インターフェイスに1つ)。クラスに両方のインターフェースを実装する場合は、クラス自体でこのメソッドをオーバーライドする必要があります。次を
tutorial

6
この答えは正確ではありません。問題は、Java 8がどのように証明するを指定することではありません。Java 8では、2つのスーパーインターフェースが異なる実装で同じメソッドを宣言できますが、インターフェースメソッドは仮想なので、問題はありません。したがって、それらを上書きするだけで問題は解決します。実際の問題は、属性のあいまいさに関するものです。これは、このあいまいさを解決して属性を上書きできないためです(属性は仮想ではありません)。
Alex Oliveira

1
「属性」とはどういう意味ですか?
Bozho

96

大学の講師の一人がこのように説明してくれました:

トースターというクラスと、NuclearBombというクラスがあるとします。彼らは両方とも「暗闇」の設定を持っているかもしれません。どちらにもon()メソッドがあります。(1つにはoff()があり、もう1つにはありません。)これらの両方のサブクラスであるクラスを作成したい場合は、ご覧のように、これは私の顔で本当に爆発する可能性がある問題です。

したがって、主な問題の1つは、2つの親クラスがある場合、同じ機能の実装が異なる可能性があることです。あるいは、私のインストラクターの例のように、同じ名前の2つの異なる機能がある可能性があります。次に、サブクラスで使用するものを決定する必要があります。これを処理する方法は確かにあります— C ++はそうします—しかし、Javaの設計者は、これにより事態が複雑になりすぎると感じました。

ただし、インターフェイスを使用すると、別のクラスの処理方法を借用するのではなく、クラスが実行できる処理を記述します。複数のインターフェースは、複数の親クラスよりも、解決が必要なトリッキーな競合を引き起こす可能性がはるかに低くなります。


5
ありがとう、NomeN。引用のソースは、当時ブレンダンバーンズという名前の博士課程の学生であり、当時オープンソースのQuake 2ソースリポジトリのメンテナーでもありました。図を行きます。
構文

4
このアナロジーの問題は、核爆弾とトースターのサブクラスを作成している場合、「核トースター」を使用すると合理的に爆発することです。
101100 2013年

20
誰かが核爆弾とトースターを混ぜることを決心した場合、彼は爆弾が彼の顔で爆破するに値する。引用は、誤った推論
マソウド

1
同じプログラミング言語では複数の「インターフェース」継承が許可されるため、この「正当化」は適用されません。
curiousguy 2013年

24

「そのメソッドは便利に見えるので、そのクラスも拡張します」と言えない場合でも、継承が過剰に使用されるためです。

public class MyGodClass extends AppDomainObject, HttpServlet, MouseAdapter, 
             AbstractTableModel, AbstractListModel, AbstractList, AbstractMap, ...

継承が過度に使用されていると言う理由を説明できますか?神クラスを作成することはまさに私がやりたいことです!静的メソッドを持つ「Tools」クラスを作成して単一継承を回避する人がたくさんいます。
Duncan Calvert

9
@DuncanCalvert:いいえ、そのコードをメンテナンスする必要がある場合は、そうしたくないのです。多くの静的メソッドはOOの要点を逃しますが、クラスが概念的に何であるかだけでなく、どこでどのコードが使用されているかを完全に失うため、過度の多重継承はさらに悪化します。どちらも「このコードを必要な場所で使用するにはどうすればよいか」という問題を解決しようとしていますが、それは単純な短期的な問題です。適切なOO設計によって解決されるはるかに困難な長期的な問題は、「プログラムを20か所で予期しない方法で中断させずにこのコードを変更するにはどうすればよいですか?」
Michael Borgwardt

2
@DuncanCalvert:凝集度が高く、カップリングが低いクラスを使用することで解決します。これは、相互に集中的に相互作用するが、小さなシンプルなパブリックAPIを通じてのみプログラムの他の部分と相互作用するデータとコードの断片を含むことを意味します。次に、内部の詳細ではなく、そのAPIの観点からそれらについて考えることができます。これは、人々が同時に限られた量の詳細しか覚えていないため、重要です。
Michael Borgwardt 2014

18

この質問の答えは、Javaコンパイラの内部動作(コンストラクタチェーン)にあります。 Javaコンパイラーの内部動作を確認する場合:

public class Bank {
  public void printBankBalance(){
    System.out.println("10k");
  }
}
class SBI extends Bank{
 public void printBankBalance(){
    System.out.println("20k");
  }
}

これをコンパイルすると、次のようになります。

public class Bank {
  public Bank(){
   super();
  }
  public void printBankBalance(){
    System.out.println("10k");
  }
}
class SBI extends Bank {
 SBI(){
   super();
 }
 public void printBankBalance(){
    System.out.println("20k");
  }
}

クラスを拡張してそのオブジェクトを作成すると、1つのコンストラクタチェーンがObjectクラスまで実行されます。

上記のコードは正常に実行されます。しかし、Car拡張Bankと呼ばれる別のクラスがあり、1つのハイブリッド(多重継承)クラスと呼ばれる場合SBICar

class Car extends Bank {
  Car() {
    super();
  }
  public void run(){
    System.out.println("99Km/h");
  }
}
class SBICar extends Bank, Car {
  SBICar() {
    super(); //NOTE: compile time ambiguity.
  }
  public void run() {
    System.out.println("99Km/h");
  }
  public void printBankBalance(){
    System.out.println("20k");
  }
}

この場合、(SBICar)はコンストラクタチェーン(コンパイル時のあいまいさ)の作成に失敗します。

インターフェースの場合、オブジェクトを作成できないため、これは許可されます。

defaultstaticメソッドの新しい概念については、インターフェイスのデフォルトを参照してください。

これでクエリが解決することを願っています。ありがとう。


7

複数のインターフェースを実装することは非常に便利であり、言語の実装者やプログラマーに多くの問題を引き起こしません。許可されています。多重継承も有用ですが、ユーザーに深刻な問題を引き起こす可能性があります(恐ろしい死のダイヤモンド)。また、多重継承で行うほとんどのことは、構成または内部クラスを使用して行うこともできます。したがって、複数の継承は、利益よりも多くの問題をもたらすため禁止されています。


「死のダイヤモンド」の何が問題なのですか?
curiousguy

2
@curiousguy同じ基本クラスの複数のサブオブジェクト、あいまいさ(どの基本クラスが使用するかをオーバーライド)、そのようなあいまいさを解決するための複雑なルールを含みます。
Tadeusz Kopec 2013年

@curiousguy:オブジェクト参照を基本型参照にキャストするとIDが維持されるとフレームワークが規定している場合、すべてのオブジェクトインスタンスは、基本クラスメソッドの実装を1つだけ持つ必要があります。場合ToyotaCarHybridCarの両方を由来CarとオーバーライドCar.Drive、および場合PriusCar継承の両方が、上書きしていないDrive、システムが仮想のものを特定する手立てもないだろうCar.Drive行う必要があります。インターフェイスは、上記のイタリック体の条件を回避することにより、この問題を回避します。
スーパーキャット2013年

1
@supercat "システムには、仮想Car.Driveが何をすべきかを識別する方法がありません。" <-または、C ++のように、コンパイルエラーが発生し、明示的に1つを選択させることもできます。
Chris Middleton

1
@ChrisMiddleton:メソッドvoid UseCar(Car &foo); ToyotaCar::Driveとのあいまいさをなくすことは期待できませんHybridCar::Drive(多くの場合、他のタイプが存在することさえ知らないか、気にする必要がないためです)。言語は、C ++と同様に、コードToyotaCar &myCarを渡したい場合、UseCar最初にHybridCarまたはToyotaCarにキャストする必要がありますが、((Car)(HybridCar)myCar).Drive`以降、((Car)(ToyotaCar)myCar).Drive さまざまなことを行うため、アップキャストを意味しますアイデンティティを保持していませんでした。
スーパーキャット2015

6

このクエリの正確な答えは、多重継承に関するOracleのドキュメントページにあります。

  1. 状態の多重継承: 複数のクラスからフィールドを継承する機能

    Javaプログラミング言語で複数のクラスを拡張できない理由の1つは、複数のクラスからフィールドを継承する機能である、状態の多重継承の問題を回避するためです。

    多重継承が許可されている場合、そのクラスをインスタンス化してオブジェクトを作成すると、そのオブジェクトはクラスのすべてのスーパークラスからフィールドを継承します。2つの問題が発生します。

    1. 異なるスーパークラスのメソッドまたはコンストラクターが同じフィールドをインスタンス化するとどうなりますか?
    2. どのメソッドまたはコンストラクターが優先されますか?
  2. 実装の多重継承:複数のクラスからメソッド定義を継承する機能

    このアプローチの問題:名前の競合あいまいさ。サブクラスとスーパークラスに同じメソッド名(およびシグニチャー)が含まれている場合、コンパイラーは呼び出すバージョンを判別できません。

    ただし、Javaは、Java 8のリリース以降に導入されたデフォルトのメソッドでこのタイプの多重継承をサポートしています。Javaコンパイラは、特定のクラスが使用するデフォルトのメソッドを決定するためのいくつかのルールを提供します。

    ダイヤモンドの問題の解決の詳細については、以下のSEの投稿を参照してください。

    Java 8の抽象クラスとインターフェースの違いは何ですか?

  3. タイプの多重継承:複数の インターフェースを実装するクラスの機能。

    インターフェースには変更可能なフィールドが含まれていないため、ここでの状態の多重継承に起因する問題について心配する必要はありません。



4

Javaは、インターフェースのみによる多重継承をサポートしています。クラスは任意の数のインターフェースを実装できますが、拡張できるクラスは1つだけです。

多重継承は致命的なダイヤモンドの問題につながるため、サポートされていません。ただし、解決することはできますが、システムが複雑になるため、Javaの創設者によって多重継承が削除されました。

1995年2月のJames Goslingによる「Java:an Overview」というタイトルのホワイトペーパーで、リンクでJavaで多重継承がサポートされない理由について説明しています。

ゴスリングによると:

「JAVAは、C ++のめったに使用されず、あまり理解されておらず、混乱している機能の多くを省略しています。C++の経験では、利益よりも悲しみをもたらします。これは、主に演算子のオーバーロード(メソッドのオーバーロードはあります)、多重継承、および広範な自動強制で構成されています。」


リンクにアクセスできません。確認して更新してください。
MashukKhan

3

同じ理由で、C#は複数の継承を許可しませんが、複数のインターフェイスを実装できます。

多重継承を伴うC ++から学んだ教訓は、それが価値よりも多くの問題を引き起こすということでした。

インターフェイスは、クラスが実装する必要があるもののコントラクトです。インターフェースから機能を得ることができません。継承により、親クラスの機能を継承できます(多重継承では、非常に混乱する可能性があります)。

複数のインターフェイスを許可すると、デザインパターン(アダプターなど)を使用して、複数の継承を使用して解決できるのと同じタイプの問題を、より信頼性が高く予測可能な方法で解決できます。


10
Javaでは許可されていないため、C#には多重継承はありません。それはJavaよりずっと後に設計されました。多重継承の主な問題は、人々が左右にそれを使用するように教えられた方法だと私は思います。ほとんどの場合、委任ははるかに優れた選択肢であるという概念は、90年代前半と90年代には存在しませんでした。したがって、私は教科書で、車がホイールとドアおよびフロントガラスである場合と、車がホイール、ドアおよびフロントガラスを含む場合の例を覚えています。したがって、Javaでの単一の継承は、その現実へのちょっとした反応でした。
Alexander Pogrebnyak

2
@AlexanderPogrebnyak:次の3つの中から2つを選択します。(1)サブタイプ参照からスーパータイプ参照への同一性保持キャストを許可します。(2)クラスが派生クラスを再コンパイルせずに仮想パブリックメンバーを追加できるようにします。(3)クラスが仮想メンバーを明示的に指定しなくても、複数の基本クラスから暗黙的に継承できるようにします。どの言語でも上記の3つすべてを管理することは不可能だと思います。Javaは#1と#2を選択し、C#もそれに続きました。C ++は#3のみを採用していると思います。個人的には、#1と#2の方が#3よりも便利だと思いますが、他のものは異なるかもしれません。
スーパーキャット2013年

@supercat「どの言語でも上記の3つすべてを管理できるとは思わない」–データメンバーのオフセットがコンパイル時ではなく実行時に決定される場合(それらはObjective-Cの「脆弱でないABIにあるため」 ")、またはクラスごとの基礎(つまり、各具象クラスには独自のメンバーオフセットテーブルがある)であれば、3つの目標すべてを達成できると思います。
常磁性クロワッサン

@TheParamagneticCroissant:主な意味上の問題は#1です。場合D1D2の両方から継承Bし、各オーバーライド機能がfあれば、とobjタイプのインスタンスであるS両方から継承D1D2けどをオーバーライドしていないがf、その後するための基準キャストSするD1何か得られるはずfの用途D1オーバーライドを、とにキャストBすべきでありませんそれを変えなさい。同様に、参照Sをキャストすると、オーバーライドD2f使用するものが生成され、それを変更しD2Bはなりません。言語が仮想メンバーの追加を許可する必要がない場合
supercat

1
@TheParamagneticCroissant:それは可能であり、私の選択肢のリストはコメントに合わせるために多少単純化されていましたが、ランタイムに延期すると、D1、D2、またはSの作成者は、中断せずに行うことができる変更を知ることができなくなりますクラスの消費者。
スーパーキャット2015

2

このトピックは近いものではないので、この回答を投稿します。Javaが多重継承を許可しない理由を誰かが理解するのに役立つことを願っています。

次のクラスを考えてみましょう:

public class Abc{

    public void doSomething(){

    }

}

この場合、Abcクラスは何も拡張しませんか?それほど高速ではないため、このクラスは暗黙的にクラスObjectを拡張し、すべてがJavaで機能できるようにする基本クラスです。すべてがオブジェクトです。

あなたがあなたのIDEは、あなたのような方法で使用できるようにすることがわかります上記のクラスを使用しようとすると:equals(Object o)toString()、などがありますが、それらのメソッドを宣言していない、彼らは基本クラスから来ましたObject

あなたは試すことができます:

public class Abc extends String{

    public void doSomething(){

    }

}

あなたのクラスは暗黙の拡張ではObjectなくString、あなたが言ったので拡張するので、これは問題ありません。次の変更を検討してください。

public class Abc{

    public void doSomething(){

    }

    @Override
    public String toString(){
        return "hello";
    }

}

これで、toString()を呼び出すと、クラスは常に「hello」を返します。

次のクラスを想像してみてください。

public class Flyer{

    public void makeFly(){

    }

}

public class Bird extends Abc, Flyer{

    public void doAnotherThing(){

    }

}

ここでも、クラスFlyerの暗黙的なメソッドを持つオブジェクトを拡張しtoString()、それらのすべてが延びているので、任意のクラスは、このメソッドを持っていますObject、あなたが呼び出す場合、そう、間接的toString()Birdその、toString()Javaが使用する必要がありますでしょうか?からAbcまたはFlyer?これは、2つ以上のクラスを拡張しようとするすべてのクラスで発生します。この種の「メソッドの衝突」を回避するために、インターフェイスの概念を構築しました。基本的に、それらはObjectを間接的に拡張しない抽象クラスと考えることができます。それらは抽象的であるため、オブジェクトであるクラスによって実装する必要があります (インターフェースだけをインスタンス化することはできません。それらはクラスによって実装される必要があります)。そのため、すべてが正常に機能し続けます。

インターフェースとクラスを区別するために、キーワードimplementsはインターフェース専用に予約されています。

デフォルトでは何も拡張しないため、同じクラスに好きなインターフェースを実装できます(ただし、別のインターフェースを拡張するインターフェースを作成することはできますが、「father」インターフェースはObjectを拡張しません)。したがって、インターフェースは単なるインターフェイスであり、「メソッドシグネチャコリジョン」の影響を受けません。コンパイラが警告をスローし、メソッドシグネチャを変更して修正する必要があります(signature = method name + params + return type) 。

public interface Flyer{

    public void makeFly(); // <- method without implementation

}

public class Bird extends Abc implements Flyer{

    public void doAnotherThing(){

    }

    @Override
    public void makeFly(){ // <- implementation of Flyer interface

    }

    // Flyer does not have toString() method or any method from class Object, 
    // no method signature collision will happen here

}

1

インターフェースは単なる契約だからです。そしてクラスは実際にはデータのコンテナです。


多重継承の根本的な問題は、クラスが独自のオーバーライド実装を提供せずに、異なる実装を行う複数のパスを介してメンバーを継承する可能性があることです。実装できるインターフェイスメンバーはクラス内のみであり、その子孫は単一継承に制限されるため、インターフェイスの継承はこれを回避します。
スーパーキャット2013年

1

たとえば、同じメソッドm1()を持つ2つのクラスA、B。また、クラスCはA、Bの両方を拡張します。

 class C extends A, B // for explaining purpose.

これで、クラスCはm1の定義を検索します。まず、見つからなかった場合はクラスを検索し、親クラスをチェックします。AとBの両方が定義を持っているので、ここでどちらの定義を選択すべきかが曖昧になります。したがって、JAVAは複数の継承をサポートしていません。


コードを変更できるように、両方の親クラスで同じメソッドまたは変数が定義されている場合、Javaコンパイラーがコンパイラーにコンパイラーを提供するのはどう
ですか?

1

次の2つの理由により、Javaは多重継承をサポートしていません。

  1. Javaでは、すべてのクラスが Object。複数のスーパークラスから継承する場合、サブクラスはオブジェクトクラスのプロパティを取得するというあいまいさを取得します。
  2. Javaでは、明示的に記述してもしなくても、すべてのクラスにコンストラクターがあります。最初のステートメントはsuper()、夕食クラスコンストラクターを呼び出すための呼び出しです。クラスに複数のスーパークラスがある場合、混乱します。

したがって、1つのクラスが複数のスーパークラスから拡張されると、コンパイル時エラーが発生します。


0

たとえば、クラスAにgetSomethingメソッドがあり、クラスBにgetSomethingメソッドがあり、クラスCがAとBを拡張している場合を考えてみます。誰かがC.getSomethingを呼び出すとどうなりますか?呼び出すメソッドを決定する方法はありません。

インターフェイスは基本的に、実装するクラスに含める必要があるメソッドを指定するだけです。複数のインターフェースを実装するクラスは、そのクラスがそれらすべてのインターフェースのメソッドを実装する必要があることを意味します。whciは、上記のような問題を引き起こしません。


2
誰かがC.getSomethingを呼び出したらどうなるでしょうか。」これはC ++のエラーです。問題が解決しました。
curiousguy

それがポイントだった...それは私が明確だと思った反例でした。どのgetメソッドが呼び出されるべきかを判断する方法がないことを指摘していました。また、補足として、質問はC ++ではなくJavaに関連していた
John Kane

申し訳ありませんが、あなたの意見はわかりません。明らかに、MIの場合にはあいまいさがあります。これは反例ですか?MIが曖昧さをもたらすことはないと主張したのは誰ですか?「また、付記として、質問はc ++ではなくjavaに関連していた」それで?
curiousguy

私はそのあいまいさが存在する理由と、インターフェースではなぜそうでないのかを示すためだけでした。
ジョンケイン

はい、MIはあいまいな呼び出しを引き起こす可能性があります。だからオーバーロードすることができます。だからJavaはオーバーロードを取り除くべきですか?
curiousguy 2013年

0

Test1、Test2、およびTest3が3つのクラスであるシナリオを考えます。Test3クラスはTest2クラスとTest1クラスを継承します。Test1クラスとTest2クラスに同じメソッドがあり、子クラスオブジェクトからそれを呼び出す場合、Test1またはTest2クラスのメソッドを呼び出すことはあいまいですが、インターフェイスには実装がないなど、インターフェイスにあいまいさはありません。


0

あいまいさの問題があるため、Javaは多重継承、マルチパス、ハイブリッド継承をサポートしていません。

 Scenario for multiple inheritance: Let us take class A , class B , class C. class A has alphabet(); method , class B has also alphabet(); method. Now class C extends A, B and we are creating object to the subclass i.e., class C , so  C ob = new C(); Then if you want call those methods ob.alphabet(); which class method takes ? is class A method or class B method ?  So in the JVM level ambiguity problem occurred. Thus Java does not support multiple inheritance.

多重継承

参照リンク: https : //plus.google.com/u/0/communities/102217496457095083679


0

誰もが知っている簡単な方法で、1つのクラスを継承(拡張)できますが、非常に多くのインターフェースを実装できます。Javaが非常に多くのクラスを拡張でき、それらが同じメソッドを持っていると仮定します。この時点で、サブクラスのスーパークラスメソッドを呼び出そうとすると、どのメソッドが実行されると思いますか?、コンパイラは混乱する例を取得し ます-複数の拡張を試みます が、これらのメソッドには、サブクラスで実装する必要のある本体がないインターフェースがあります。 複数の実装を試してみてください


1
ここにそれらの例を投稿するだけです。それがなければ、これは9歳の質問に対するテキストのブロックのように見えます。
ポクムルニク

-1

* Javaの初心者なので、これは簡単な答えです*

3つのクラスXYあり、Z

以下のように継承され、私たちはそうX extends Y, Z 両方YZ方法を持っているalphabet()同じ戻り値の型と引数を持ちます。このメソッドalphabet()Yは、最初のアルファベット表示し、メソッドアルファベットでZは、最後のアルファベットを表示します。したがって、alphabet()がを呼び出すと、あいまいさが生じXます。それが最初または最後のアルファベットを表示するように言うかどうか??? したがって、Javaは多重継承をサポートしていません。インタフェースの場合には、考えるYZインターフェースします。したがって、どちらにもメソッドの宣言は含まれますalphabet()が、定義は含まれません。最初のアルファベットと最後のアルファベットのどちらを表示するかはわかりませんが、メソッドを宣言するだけですalphabet()。したがって、あいまいさを高める理由はありません。クラス内で必要なものを使用してメソッドを定義できますX

つまり、インターフェイスの定義は実装後に行われるため、混乱はありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.