JavaまたはC#で多重継承が許可されないのはなぜですか?


115

JavaとC#では多重継承が許可されていないことを知っています。多くの本では、多重継承は許可されていません。ただし、インターフェイスを使用して実装できます。それが許可されない理由については何も議論されていません。なぜそれが許可されていないのか正確に誰かに教えてもらえますか?


1
ただ、そこにあることを指摘したいとしている C#クラスでMI様行動できるようにそこにフレームワークが。もちろん、ステートレスミックスイン(拡張メソッドを使用)を使用するオプションもあります。ただし、ステートレスであるため、あまり有用ではありません。
Dmitri Nesteruk、2009年

1
インターフェースが継承と関係があることは、言語構文によって作成された幻想です。継承はあなたをより豊かにすることになっています。インターフェースについてはそのようなことは何もありません、あなたはスクワットを継承せず、それはあなたを著しく貧弱にします。あなたはハリーおじさんのギャンブルの借金を相続しました、あなたはインターフェースを実装するためにやるべきことがもっとあります。
Hans Passant 2017

回答:


143

短い答えは、言語デザイナーはそうしないことを決めたからです。

基本的に、.NETデザイナーとJavaデザイナーの両方が多重継承を許可していないように思われました。MIを追加すると、言語の複雑さが増しすぎて、メリットがほとんどなくなったためです。

より楽しく、詳細に読むために、言語デザイナーの何人かへのインタビューをウェブ上で利用できるいくつかの記事があります。たとえば、.NETの場合、MSでCLRを担当したChris Brumme氏は、なぜ次のようにしないことにしたのかを説明しています。

  1. 言語が異なれば、MIの動作に対する期待も異なります。たとえば、競合がどのように解決されるか、重複するベースがマージされるか冗長であるか。CLRにMIを実装する前に、すべての言語の調査を行い、共通の概念を理解し、言語に依存しない方法でそれらを表現する方法を決定する必要があります。また、MIがCLSに属しているかどうか、およびこの概念を必要としない言語(おそらくVB.NETなど)にとってこれが何を意味するかも決定する必要があります。もちろん、それは私たちが共通言語ランタイムとして使用しているビジネスですが、MIについてはまだ実行していません。

  2. MIが本当に適切な場所の数は実際には非常に少ないです。多くの場合、複数のインターフェースを継承すると、代わりにジョブを実行できます。他の場合では、カプセル化と委任を使用できる場合があります。ミックスインのように少し異なる構成を追加した場合、それは実際により強力になるでしょうか?

  3. 複数の実装の継承は、実装に多くの複雑さをもたらします。この複雑さは、キャスト、レイアウト、ディスパッチ、フィールドアクセス、シリアル化、ID比較、検証可能性、リフレクション、ジェネリック、およびおそらく他の多くの場所に影響を与えます。

ここで記事全体を読むことができます。

Javaの場合、次の記事を読むことができます。

Java言語からの多重継承を省略した理由は、主に「単純でオブジェクト指向で使い慣れた」という目標に由来しています。単純な言語として、Javaの作成者は、ほとんどの開発者が大規模なトレーニングなしで理解できる言語を求めていました。そのために、彼らはC ++の不必要な複雑さ(単純)を引き継ぐことなく、言語をC ++にできるだけ似せる(使い慣れた)ように努めました。

設計者の意見では、多重継承はそれが解決するよりも多くの問題と混乱を引き起こします。したがって、それらは言語からの多重継承を削減します(演算子のオーバーロードを削減するのと同じように)。設計者のC ++の豊富な経験から、多重継承は頭痛の種に値しないだけであることがわかりました。


10
いい比較です。私が考える両方のプラットフォームの根底にある思考プロセスをかなり示しています。
CurtainDog 2009年

97

実装の多重継承は許可されていません。

問題は、カウボーイクラスとアーティストクラスがあり、両方にdraw()メソッドの実装がある場合、コンパイラー/ランタイムが何をすべきかを理解できず、新しいCowboyArtistタイプを作成しようとすることです。draw()メソッドを呼び出すとどうなりますか?誰かが通りで死んで横たわっていますか、それとも素敵な水彩画を持っていますか?

それはダブルダイアモンド継承問題と呼ばれていると思います。


80
路上で死んだ誰かの素敵な水彩画を手に入れます:-)
Dan F

これが唯一の問題ですか?C ++が仮想キーワードによってこの問題を解決するかどうかはわかりませんが、これは本当ですか?私はC ++が苦手です
Abdulsattar Mohammed

2
C ++では、派生で呼び出す基本クラス関数を指定するか、自分で再実装することができます。
ドミトリーRisenberg

1
現代のデザインは、最も単純な場合を除いて、継承よりも構成を優先する傾向があります。多重継承が単純なケースとして数えられることは決してなかったでしょう。コンポジションを使用すると、このようなダイヤモンドの問題が発生したときにクラスが行うことを正確に制御できます...
ビルミシェル

優先度リストが必要なだけです。これはCommon LispのオブジェクトシステムであるCLOSで使用されます。すべてのクラスは階層を形成し、呼び出されるメソッドはそこから決定されます。さらに、CLOSでは、CowboyArtist.draw()が最初にカウボーイを描画し、次にアーティストを描画するように、さまざまなルールを定義できます。またはあなたが何を補うのか。
セバスチャンクロッグ

19

理由: Javaはシンプルであるため、非常に人気があり、コーディングが簡単です。

そのため、Java開発者はプログラマにとって理解するのが困難で複雑だと感じたことを避け、それを避けようとしました。そのような種類のプロパティの1つは多重継承です。

  1. 彼らはポインタを避けました
  2. 彼らは多重継承を避けました。

多重継承の問題ダイヤモンドの問題。

  1. クラスAにメソッドfun()があるとします。クラスBとクラスCはクラスAから派生しています。
  2. また、クラスBとCの両方がメソッドfun()をオーバーライドします。
  3. ここで、クラスDがクラスBとCの両方を継承するとします(仮定のみ)。
  4. クラスDのオブジェクトを作成します。
  5. D d =新しいD();
  6. そして、d.fun();にアクセスしてみます。=>クラスBのfun()またはクラスCのfun()を呼び出しますか?

これは、ダイヤモンドの問題に存在する曖昧さです。

この問題を解決することは不可能ではありませんが、それを読んでいる間、プログラマーはより多くの混乱と複雑さを生み出します。 解決しようとする以上の問題を引き起こします。

:ただし、インターフェイスを使用して間接的に常に複数の継承を実装する方法はありません。


1
「しかし、インターフェースを使用することで、間接的に常に複数の継承を実装することができます。」-プログラマがメソッド本体を毎回何度も指定する必要があります。
カリ

13

Javaの設計哲学はC ++と大きく異なるためです。(ここではC#については説明しません。)

C ++の設計では、Stroustrupは、それらがどのように悪用される可能性があるかに関係なく、有用な機能を含めたかった。多重継承、演算子のオーバーロード、テンプレート、およびその他のさまざまな機能を使用して、時間を浪費することは可能ですが、それらを使用して非常に優れた機能を実行することも可能です。

Javaの設計哲学は、言語構造の安全性を強調することです。その結果、実行するのがはるかに厄介なことがありますが、あなたが見ているコードがあなたが考えていることを意味していることをはるかに確信することができます。

さらに、Javaは、C ++とSmalltalk(最もよく知られているOO言語)からの大部分の反応でした。他のオブジェクト指向言語はたくさんあり(Common Lispは実際には最初に標準化された言語です)、MIをより適切に処理するオブジェクト指向システムがいくつかあります。

言うまでもなく、インターフェース、構成、および委任を使用して、JavaでMIを実行することは完全に可能です。これはC ++よりも明示的であるため、使用するのが不格好ですが、一見して理解しやすいものを入手できます。

ここで正解はありません。答えはさまざまですが、特定の状況にどちらが適しているかは、アプリケーションと個人の好みによって異なります。


12

人々がMIから離れる主な(決して唯一ではありませんが)理由は、実装のあいまいさをもたらす、いわゆる「ダイヤモンドの問題」です。このウィキペディアの記事はそれについて議論し、私が説明できるよりもよく説明しています。MIはさらに複雑なコードにつながる可能性があり、多くのOO設計者はMIは不要であり、それを使用する場合、モデルはおそらく間違っていると主張します。この最後の点に同意するかどうかはわかりませんが、物事をシンプルに保つことは常に良い計画です。


8

C ++では、不適切に使用すると多重継承が大きな頭痛の種でした。これらの一般的な設計問題を回避するために、複数のインターフェース「継承」が代わりに最新の言語(java、C#)で強制されました。


8

多重継承は

  • わかりにくい
  • デバッグするのが難しい(たとえば、同じ名前のメソッドを持つ複数のフレームワークのクラスを深く組み合わせると、予期しない相乗効果が発生する可能性があります)
  • 誤用しやすい
  • 本当にないことに有用
  • あなたはそれが正しく行わたい場合は特に、実装するのは難しい効率的に

したがって、Java言語に多重継承を含めないことは、賢明な選択と考えることができます。


3
Common Lisp Object Systemで作業したことで、上記のすべてに同意しません。
David Thornley、

2
動的言語はカウントしません;-) Lispのようなシステムでは、デバッグをかなり簡単にするREPLがあります。また、CLOS(私が理解しているように、私は実際に使用したことがなく、それについて読んだだけです)は、非常に柔軟性が高く、独自の態度を持つメタオブジェクトシステムです。しかし、C ++のような静的にコンパイルされた言語を考えてみてください。コンパイラーは、複数の(重複している可能性がある)vtableを使用して、非常に複雑なメソッド検索を生成します。
mfx

5

もう1つの理由は、単一継承によってキャストが簡単になり、アセンブラー命令が発行されない(必要なタイプの互換性を確認する以外は)ことです。複数の継承がある場合は、子クラスで特定の親がどこから始まるかを把握する必要があります。したがって、パフォーマンスは確かに特典です(ただし、唯一のものではありません)。


4

昔(70年代)は、コンピュータサイエンスの方が科学で量産が少なかったため、プログラマは優れた設計と優れた実装について考える時間を持っていたため、製品(プログラム)は高品質(TCP / IP設計など)でした。および実装)。今日、誰もがプログラミングしていて、マネージャーが期限前に仕様を変更しているとき、ウィキペディアのリンクにあるSteve Haighの投稿にあるような微妙な問題を追跡することは困難です。したがって、「多重継承」はコンパイラーの設計によって制限されます。あなたがそれを好きなら、あなたはまだC ++を使うことができます...そしてあなたが望むすべての自由を持っています:)


4
...何度も何度も足を撃つ自由を含む;)
マリオオルテゴン09年

4

「Javaでは多重継承は許されない」という言葉を少々気にしないでください。

多重継承は、「タイプ」が複数の「タイプ」から継承する場合に定義されます。また、インターフェースには動作があるため、タイプとしても分類されます。したがって、Javaには多重継承があります。それだけが安全です。


6
しかし、インターフェースから継承するのではなく、実装します。インターフェイスはクラスではありません。
Blorgbeardは、

2
はい。しかし、いくつかの予備的な本を除いて、継承はそれほど狭く定義されていません。そして、文法を超えて見るべきだと思います。どちらも同じことを行い、インターフェースはそのためだけに「発明」されました。
Rig Veda、

インターフェースCloseableを取ります:1つのメソッド、close()。これをOpenableに拡張するとします。これには、ブールフィールドisOpenをtrueに設定するopen()メソッドがあります。すでに開いているOpenableを開こうとすると、例外がスローされます。開いていないOpenableを閉じようとすると、クラスの何百もの興味深い系統図がそのような機能を採用したい場合があります...それぞれを拡張する必要はありません。クラスOpenableからの時間。Openableが単なるインターフェースの場合、この機能は提供できません。QED:インターフェースは継承を提供しません!
マイクげっ歯類2016

4

クラスを動的にロードすると、多重継承の実装が困難になります。

Javaでは実際に、単一の継承とインターフェースを使用することにより、複数の継承の複雑さを回避しました。以下に説明するような状況では、多重継承の複雑さが非常に高い

多重継承のダイアモンド問題。 Aから継承する2つのクラスBとCがあります。BとCが継承されたメソッドをオーバーライドし、独自の実装を提供するとします。ここで、Dは多重継承を行うBとCの両方から継承します。Dはそのオーバーライドされたメソッドを継承する必要があります。jvmはどのオーバーライドされたメソッドを使用するかを決定できませんか?

c ++では、仮想関数を使用して処理するため、明示的に行う必要があります。

これは、インターフェースを使用することで回避できます。メソッド本体はありません。インターフェイスをインスタンス化することはできません。クラスによって実装するか、他のインターフェイスによって拡張することしかできません。


3

実際、継承されたクラスが同じ機能を持つ場合、多重継承は複雑になります。つまり、コンパイラは混乱を招くでしょう(ダイヤモンドの問題)。したがって、Javaではその複雑さが取り除かれ、多重継承のような機能を実現するためのインターフェースが提供されました。インターフェースが使える


2

Javaには、ポリモーフィズムという概念があります。Javaには2種類のポリモーフィズムがあります。メソッドのオーバーロードとメソッドのオーバーライドがあります。その中でも、メソッドのオーバーライドはスーパークラスとサブクラスの関係で発生します。サブクラスのオブジェクトを作成してスーパークラスのメソッドを呼び出す場合、サブクラスが複数のクラスを拡張する場合、どのスーパークラスメソッドを呼び出す必要がありますか?

または、スーパークラスコンストラクターを呼び出しながら super()、どのスーパークラスコンストラクターが呼び出されますか?

この決定は、現在のJava API機能では不可能です。そのため、Javaでは多重継承は許可されていません。


基本的に、Javaの型システムは、すべてのオブジェクトインスタンスが1つの型を持っていると想定し、オブジェクトをスーパータイプにキャストすると常に機能し、参照を維持します。インスタンスがそのサブタイプまたはサブタイプの場合、参照をサブタイプにキャストすると参照が維持されます。そのサブタイプ。そのような仮定は有用であり、それらと一貫した方法で一般化された多重継承を許可することは可能ではないと思います。
スーパーキャット2012年

1

複数の継承はJavaでは直接許可されていませんが、インターフェースを介して許可されます。

理由:

多重継承:複雑さとあいまいさを導入します。

インターフェース:インターフェースは、Javaの完全に抽象的なクラスであり、プログラムの構造または内部の動作を、一般に利用可能なインターフェースから適切に描くための統一された方法を提供し、その結果、より多くの柔軟性と再利用可能なコードおよびより多くの制御が得られます。他のクラスをどのように作成して操作するかについて。

より正確には、これらはJavaの特別な構成要素であり、一種の多重継承、つまり複数のクラスにアップキャストできるクラスを実行できるようにする追加の特性を備えています。

簡単な例を見てみましょう。

  1. メソッド名は同じで機能が異なる2つのスーパークラスクラスAとBがあるとします。(extends)キーワードを使用した次のコードでは、多重継承はできません。

       public class A                               
         {
           void display()
             {
               System.out.println("Hello 'A' ");
             }
         }
    
       public class B                               
          {
            void display()
              {
                System.out.println("Hello 'B' ");
              }
          }
    
      public class C extends A, B    // which is not possible in java
        {
          public static void main(String args[])
            {
              C object = new C();
              object.display();  // Here there is confusion,which display() to call, method from A class or B class
            }
        }
  2. しかし、インターフェースを介して、(implements)キーワードを使用して多重継承が可能です。

    interface A
        {
           // display()
        }
    
    
     interface B
        {
          //display()
        }
    
     class C implements A,B
        {
           //main()
           C object = new C();
           (A)object.display();     // call A's display
    
           (B)object.display(); //call B's display
        }
    }

0

なぜそれが許可されていないのか正確に誰かに教えてもらえますか?

このドキュメントのリンクから回答を見つけることができます

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

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

  1. 異なるスーパークラスのメソッドまたはコンストラクターが同じフィールドをインスタンス化するとどうなりますか?

  2. どのメソッドまたはコンストラクターが優先されますか?

状態の多重継承が許可されていますが、それでも実装できます

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

実装の複数の継承(インターフェースのデフォルトのメソッドによる):複数のクラスからメソッド定義を継承する機能

追加情報については、この関連するSEの質問を参照してください。

インターフェースによる多重継承のあいまいさ


0

C ++では、クラスは複数のクラスから(直接的または間接的に)継承できます。これは多重継承と呼ばれ ますます。ます。

ただし、C#およびJavaでは、クラスが単一継承に限定され、各クラスは単一の親クラスから継承されます。

多重継承は、2つの異なるクラス階層の側面を組み合わせるクラスを作成するための便利な方法です。これは、単一のアプリケーション内で異なるクラスフレームワークを使用するときによく発生します。

たとえば、2つのフレームワークが例外の独自の基本クラスを定義する場合、多重継承を使用して、いずれかのフレームワークで使用できる例外クラスを作成できます。

多重継承の問題は、あいまいになる可能性があることです。古典的な例は、クラスが他の2つのクラスを継承し、それぞれが同じクラスから継承する場合です。

class A {
    protected:
    bool flag;
};
class B : public A {};
class C : public A {};
class D : public B, public C {
    public:
    void setFlag( bool nflag ){
        flag = nflag; // ambiguous
    }
};

この例では、flagデータメンバーはによって定義されていclass Aます。しかしclass Dから下降class B し、class C両方の派生から、Aその本質的には、二つのコピーのは、flagの2つのインスタンスがあるため用意されていAているDのクラス階層。どれを設定しますか?コンパイラは、flagin への参照Dあいまいであることを報告します。1つの修正は、明示的に参照を明確にすることです。

B::flag = nflag;

別の修正は、BとCを次のように宣言することです。 virtual base classesすることです。つまり、Aのコピーは階層内に1つしか存在できず、あいまいさを排除します。

派生オブジェクトの構築時に基本クラスが初期化される順序や、メンバーが誤って派生クラスから隠される方法など、他の複雑さは複数の継承に存在します。これらの複雑さを回避するために、一部の言語はより単純な単一継承モデルに制限されています。

これは継承をかなり簡略化しますが、共通の祖先を持つクラスのみが動作を共有できるため、その有用性も制限されます。インターフェイスは、異なる階層のクラスが、コードを共有することで実装されていない場合でも、共通のインターフェイスを公開できるようにすることで、この制限をある程度緩和します。


-1

この例を想像してください:私はクラスを持っています Shape1

それにはCalcualteArea方法があります:

Class Shape1
{

 public void CalculateArea()

     {
       //
     }
}

Shape2同じメソッドを持つ別のクラスがあります

Class Shape2
{

 public void CalculateArea()

     {

     }
}

これでCircleクラスの子クラスができました。これはShape1とShape2の両方から派生しています。

public class Circle: Shape1, Shape2
{
}

ここで、Circleのオブジェクトを作成してメソッドを呼び出しても、システムはどの計算領域メソッドを呼び出すのかわかりません。どちらにも同じ署名があります。そのため、コンパイラは混乱します。そのため、複数の継承は許可されていません。

ただし、インターフェースにはメソッド定義がないため、複数のインターフェースが存在する可能性があります。どちらのインターフェースにも同じメソッドがありますが、どちらにも実装がなく、常に子クラスのメソッドが実行されます。


言語デザイナーは、インターフェースで行うように、明示的なメソッド呼び出しを与えることができます。そのようなポイントはありません!もう1つの理由は、抽象メソッドを含む抽象クラスが、それをどのように考えるかです。
Nomi Ali
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.