JavaとC#では多重継承が許可されていないことを知っています。多くの本は、多重継承は許可されていないと言っています。ただし、インターフェイスを使用して実装できます。なぜそれが許可されないのかについては何も議論されていません。なぜ許可されないのか正確に教えてもらえますか?
17 に答える
簡単に言えば、言語設計者がそうしないことにしたからです。
基本的に、.NET と Java の両方の設計者が多重継承を許可しなかったようです。なぜなら、MI を追加すると言語が複雑になりすぎてメリットが少なすぎると考えたからです。
より楽しく詳細な読み物として、言語設計者のインタビューを掲載した記事が Web で入手できます。たとえば、.NET については、Chris Brumme (MS で CLR の作業を行っていた) が、そうしないことにした理由を次のように説明しています。
MI がどのように機能するかについて、実際には言語が異なれば期待も異なります。たとえば、競合がどのように解決されるか、重複した塩基がマージされるか冗長になるかなどです。CLR に MI を実装する前に、すべての言語を調査し、共通の概念を見つけ出し、それらを言語に依存しない方法で表現する方法を決定する必要があります。また、MI が CLS に属するかどうか、およびこの概念を必要としない言語 (おそらく VB.NET など) にとってこれが何を意味するかを決定する必要があります。もちろん、これは共通言語ランタイムとして私たちが取り組んでいるビジネスですが、MI についてはまだ実行に移していません。
MI が真に適切な場所は、実際には非常に少ないです。多くの場合、代わりに複数のインターフェイスの継承を使用して作業を完了できます。それ以外の場合は、カプセル化と委任を使用できる場合があります。ミックスインのような少し異なる構造を追加すると、実際にはより強力になるでしょうか?
複数の実装の継承により、実装が非常に複雑になります。この複雑さは、キャスト、レイアウト、ディスパッチ、フィールド アクセス、シリアライゼーション、ID 比較、検証可能性、リフレクション、ジェネリック、およびおそらく他の多くの場所に影響を与えます。
Java については、次の記事を参照してください。
Java 言語から多重継承を省略する理由のほとんどは、「単純で、オブジェクト指向で、親しみやすい」という目標に由来します。単純な言語として、Java の作成者は、ほとんどの開発者が広範なトレーニングを受けなくても理解できる言語を望んでいました。そのために、彼らは C++ の不必要な複雑さ (単純さ) を持ち越すことなく、可能な限り C++ に似た (親しみやすい) 言語にするように努めました。
設計者の意見では、多重継承は解決するよりも多くの問題と混乱を引き起こします。そのため、言語から多重継承をカットしました (演算子のオーバーロードをカットしたのと同じように)。設計者は C++ の豊富な経験から、多重継承は頭を悩ます価値がないことを学びました。
実装の多重継承は許可されていません。
問題は、Cowboy クラスと Artist クラスの両方に draw() メソッドの実装があり、新しい CowboyArtist 型を作成しようとすると、コンパイラ/ランタイムが何をすべきかを判断できないことです。draw() メソッドを呼び出すとどうなりますか? 誰かが路上で死んで横たわっていますか、それとも素敵な水彩画を持っていますか?
私はそれが二重ダイヤモンド継承問題と呼ばれていると信じています。
理由: Java は非常に人気があり、その単純さからコーディングも簡単です。
そのため、Java 開発者がプログラマーにとって理解するのが難しくて複雑だと感じたとしても、彼らはそれを避けようとしました。そのような種類のプロパティの 1 つが多重継承です。
- 彼らはポインターを避けた
- 彼らは多重継承を避けました。
多重継承の問題:ダイアモンドの問題。
例:
- クラス A にメソッド fun() があるとします。クラス B とクラス C はクラス A から派生します。
- そして、クラス B と C の両方がメソッド fun() をオーバーライドします。
- ここで、クラス D がクラス B と C の両方を継承すると仮定します (単なる仮定)。
- クラス D のオブジェクトを作成します。
- D d = 新しい D();
- d.fun(); にアクセスしてみてください。=> クラス B の fun() またはクラス C の fun() を呼び出しますか?
これがダイヤモンド問題に存在する曖昧さです。
この問題を解決することは不可能ではありませんが、それを読むと、プログラマーがより混乱し、複雑になります。 解決しようとするよりも多くの問題を引き起こします。
注: ただし、どのような方法でも、インターフェイスを使用して間接的に多重継承をいつでも実装できます。
人々が MI から遠ざかる主な (決して唯一ではない) 理由は、いわゆる「ダイヤモンド問題」であり、実装が曖昧になります。このウィキペディアの記事では、それについて議論し、私ができるよりもよく説明しています. また、MI はより複雑なコードにつながる可能性があり、多くの OO 設計者は MI は必要ないと主張しており、MI を使用する場合、モデルはおそらく間違っています。この最後の点に同意するかどうかはわかりませんが、物事をシンプルに保つことは常に良い計画です。
C++ では、多重継承は、不適切に使用すると大きな頭痛の種でした。これらの一般的な設計上の問題を回避するために、最新の言語 (Java、C#) では代わりに複数のインターフェイスの「継承」が強制されました。
多重継承は
- わかりにくい
- デバッグが困難 (たとえば、同じ名前のメソッドを持つ複数のフレームワークのクラスを奥深くで混在させると、まったく予想外の相乗効果が生じる可能性があります)
- 誤用しやすい
- あまり役に立たない
- 特に正しく効率的に実行したい場合は、実装が難しい
したがって、多重継承を Java 言語に含めないことは賢明な選択であると考えることができます。
もう 1 つの理由は、単一継承によってキャストが簡単になり、アセンブラー命令を発行しないことです (必要に応じて型の互換性をチェックする以外は)。複数の継承がある場合は、特定の親が子クラスのどこから始まるかを把握する必要があります。したがって、パフォーマンスは確かに特典です (ただし、唯一のものではありません)。
昔 ('70 年代) に戻ると、コンピューター サイエンスはより科学的で大量生産が少なく、プログラマーは優れた設計と優れた実装について考える時間があり、その結果、製品 (プログラム) は高品質 (例: TCP/IP 設計) でした。および実装)。現在、誰もがプログラミングを行っており、管理者が締め切り前に仕様を変更しているため、Steve Haigh の投稿のウィキペディア リンクで説明されているような微妙な問題を追跡するのは困難です。したがって、「多重継承」はコンパイラの設計によって制限されます。あなたがそれを気に入れば、あなたはまだC++を使うことができます....そしてあなたが望むすべての自由があります:)
「Java では複数の継承は許可されていません」というステートメントを塩のピンチで受け取ります。
多重継承は、「タイプ」が複数の「タイプ」から継承する場合に定義されます。また、インターフェイスも動作があるため、タイプとして分類されます。したがって、Java には多重継承があります。その方が安全だというだけです。
クラスを動的にロードすると、多重継承の実装が難しくなります。
実際、Java では、単一の継承とインターフェイスを使用することで、複数の継承の複雑さを回避しました。以下に説明するような状況では、多重継承の複雑さが非常に高くなります
多重継承のダイヤモンド問題。 A から継承する 2 つのクラス B と C があります。B と C が継承されたメソッドをオーバーライドし、独自の実装を提供するとします。現在、D は多重継承を行う B と C の両方から継承しています。Dはそのオーバーライドされたメソッドを継承する必要があります.jvmはどのオーバーライドされたメソッドが使用されるかを決定できませんか?
C++ では、処理に仮想関数が使用され、明示的に行う必要があります。
これはインターフェイスを使用することで回避できます。メソッド本体はありません。インターフェイスはインスタンス化できません。クラスによって実装されるか、他のインターフェイスによって拡張されるだけです。
継承されたクラスが同じ機能を持つ場合、実際には多重継承が複雑になります。つまり、コンパイラはどちらを選択する必要があるかで混乱します (ダイアモンド問題)。そのため、Java では、その複雑さが取り除かれ、多重継承のような機能を取得するためのインターフェイスが提供されました。インターフェイスを使用できます
Javaには概念、つまりポリモーフィズムがあります。Javaには2種類のポリモーフィズムがあります。メソッドのオーバーロードとメソッドのオーバーライドがあります。その中で、メソッドのオーバーライドはスーパークラスとサブクラスの関係で発生します。サブクラスのオブジェクトを作成してスーパークラスのメソッドを呼び出す場合、およびサブクラスが複数のクラスを拡張する場合、どのスーパークラスメソッドを呼び出す必要がありますか?
または、によってスーパークラスコンストラクターを呼び出しているときにsuper()
、どのスーパークラスコンストラクターが呼び出されますか?
この決定は、現在のJavaAPI機能では不可能です。したがって、Javaでは多重継承は許可されていません。
この例を想像してください: 私はクラスを持っていますShape1
CalcualteArea
メソッドがあります:
Class Shape1
{
public void CalculateArea()
{
//
}
}
Shape2
同じメソッドを持つ別のクラスがあります
Class Shape2
{
public void CalculateArea()
{
}
}
これで子クラス Circle ができました。これは Shape1 と Shape2 の両方から派生しています。
public class Circle: Shape1, Shape2
{
}
Circle のオブジェクトを作成してメソッドを呼び出すと、システムはどの計算面積メソッドが呼び出されるかわかりません。どちらも同じサインです。したがって、コンパイラは混乱します。そのため、複数の継承は許可されていません。
ただし、インターフェイスにはメソッド定義がないため、複数のインターフェイスが存在する可能性があります。両方のインターフェースが同じメソッドを持っていても、どちらも実装がなく、常に子クラスのメソッドが実行されます。