Java and C#。では多重継承が許可されていないことを知っています。多くの本は、多重継承は許可されていません。しかし、インターフェースを使用して実装できます。許可されていない理由を正確に教えてもらえますか?
簡単な答えは、言語設計者がそうしないことに決めたからです。
基本的に、.NETとJavaデザイナーは、MIを追加すると複雑さが増しすぎると推論したため、多重継承を許可しなかったようです言語にあまり利益がありません。
より楽しく、詳細に読むために、いくつかの言語デザイナーのインタビューを掲載したウェブ上の記事がいくつかあります。たとえば、.NETの場合、MSでCLRを担当していたChris Brummeは、次のようにしないことにした理由を説明しています。
実際、MIはどのように機能するかについて、言語ごとに期待が異なります。たとえば、競合がどのように解決されるか、重複するベースがマージされるか冗長であるかなど。 MIをCLRに実装する前に、すべての言語の調査を行い、共通の概念を理解し、それらを言語に依存しない方法で表現する方法を決定する必要があります。また、MIがCLSに属しているかどうか、およびこの概念を必要としない言語(たとえばVB.NETなど)にとってこれが何を意味するかを決定する必要があります。もちろん、それは共通言語ランタイムとして私たちが行っているビジネスですが、まだMIでそれを行うには至っていません。
MIが本当に適切な場所の数は実際には非常に少ないです。多くの場合、代わりに複数のインターフェイスの継承によりジョブを完了できます。また、カプセル化と委任を使用できる場合もあります。ミックスインのようなわずかに異なる構成を追加する場合、実際にはより強力になりますか?
複数の実装の継承は、実装に多くの複雑さを注入します。この複雑さは、キャスト、レイアウト、ディスパッチ、フィールドアクセス、シリアル化、ID比較、検証可能性、リフレクション、ジェネリック、およびおそらく他の多くの場所に影響を与えます。
Javaの場合、 この記事 を読むことができます。
Java言語から複数の継承を省略する理由は、ほとんどの場合、「単純でオブジェクト指向で使い慣れた」目標に由来します。単純な言語として、Javaの作成者はほとんどの開発者がそのため、C++の不必要な複雑さ(単純な)を引き継ぐことなく、言語をできるだけC++に似た(馴染みのある)言語にするように努力しました。
設計者の意見では、多重継承は解決するよりも多くの問題と混乱を引き起こします。そのため、言語からの多重継承を削減します(演算子のオーバーロードを削減するのと同じように)。設計者の広範なC++の経験から、多重継承は頭痛の種に値しないことがわかりました。
実装の多重継承は許可されていません。
問題は、どちらもdraw()メソッドの実装を持つCowboyクラスとArtistクラスがあり、新しいCowboyArtistタイプを作成しようとすると、コンパイラ/ランタイムが何をすべきかを判断できないことです。 draw()メソッドを呼び出すとどうなりますか?誰かが通りで死んで横たわっている、またはあなたは素敵な水彩画を持っていますか?
これは二重ダイヤモンド継承問題と呼ばれています。
理由: Javaは非常に人気があり、シンプルであるためコーディングが簡単です。
そのため、プログラマにとってJava開発者が理解するのが困難で複雑だと感じるものは、それを避けようとしました。そのような種類のプロパティの1つに、多重継承があります。
多重継承の問題:ダイヤモンドの問題。
例:
これは、ダイヤモンドの問題に存在するあいまいさです。
この問題を解決することは不可能ではありませんが、それを読んでいる間、プログラマにさらなる混乱と複雑さをもたらします。 解決しようとするよりも多くの問題を引き起こします。
注:しかし、インターフェースを使用して間接的に複数の継承をいつでも実装できる方法。
JavaはC++とは設計思想が大きく異なるためです。ここではC#について説明しません。)
C++の設計において、Stroustrupは、どのように誤用される可能性があるかに関係なく、有用な機能を含めることを望んでいました。多重継承、演算子のオーバーロード、テンプレート、およびその他のさまざまな機能を使用して大きな時間を台無しにすることは可能ですが、それらを使用して非常に良いことを行うことも可能です。
Java設計哲学は、言語構造の安全性を強調することです。その結果、やるのがはるかに厄介なことがありますが、コードが再見るとは、あなたがそれが何をするかを意味します。
さらに、JavaはC++とSmalltalkからの反応であり、最もよく知られているOO言語。他にも多くのOO言語(Common LISPは実際には標準化された最初の言語でした)、MIをより適切に処理する異なるOOシステム。
インターフェース、構成、および委任を使用して、JavaでMIを実行することは完全に可能であることは言うまでもありません。 C++よりも明示的であるため、使いにくいですが、一見すると理解しやすいものが得られます。
ここには正しい答えはありません。さまざまな答えがあり、特定の状況でどちらが良いかは、アプリケーションと個人の好みによって異なります。
人々がMIから遠ざかる主な(決して唯一ではありませんが)理由は、実装のあいまいさをもたらすいわゆる「ダイヤモンドの問題」です。この wikipediaの記事 はそれについて議論し、私ができるよりもうまく説明しています。 MIはより複雑なコードにもつながる可能性があり、多くのOOデザイナーはMIは必要ないと主張します。この最後の点に同意しますが、物事をシンプルにすることは常に良い計画です。
C++では、不適切に使用された場合、多重継承は大きな頭痛の種でした。これらの一般的なデザインの問題を回避するために、現代の言語(Java、C#)では複数のインターフェイスの代わりに「継承」が強制されました。
多重継承は
したがって、notに複数の継承をJava言語に含めることをお勧めします。
もう1つの理由は、単一継承により、キャストが簡単になり、アセンブラー命令が発行されないことです(必要に応じて型の互換性をチェックする以外)。複数の継承がある場合、特定の親が子クラスのどこから始まるかを把握する必要があります。したがって、パフォーマンスは確かに特典です(唯一ではありませんが)。
クラスを動的にロードすると、多重継承の実装が難しくなります。
In Java実際には、単一の継承とインターフェースを使用することで、多重継承の複雑さを回避しました。以下に説明するような状況では、多重継承の複雑さが非常に高くなります。
多重継承のダイヤモンド問題Aから継承するBとCの2つのクラスがあります。BとCがメソッドを継承し、独自の実装を提供します。現在、DはBとCの両方から多重継承を継承しています。 Dはそのオーバーライドされたメソッドを継承する必要があり、jvmはどのオーバーライドされたメソッドを使用するかを決定できませんか?
C++では、仮想関数を使用して処理するため、明示的に行う必要があります。
これは、インターフェースを使用することで回避できます。メソッド本体はありません。インターフェイスをインスタンス化することはできません。クラスによってのみ実装したり、他のインターフェイスによって拡張したりできます。
昔(70年代)、コンピューターサイエンスがより科学的で量産が少なかった頃、プログラマーは優れた設計と優れた実装について考える時間を持っていました。その結果、製品(プログラム)は高品質(例えばTCP/IP設計)および実装)。今日、誰もがプログラミングを行っており、マネージャーが期限前に仕様を変更しているとき、Steve Haighの投稿からウィキペディアのリンクに記載されているような微妙な問題は追跡が困難です。したがって、「多重継承」はコンパイラの設計によって制限されます。あなたがそれを好めば、あなたはまだC++を使用することができます....そしてあなたが望むすべての自由を持っています:)
私は、「Javaでは多重継承は許可されていません」とひと言で言います。
「タイプ」が複数の「タイプ」から継承する場合、多重継承が定義されます。また、インターフェースも動作するため、タイプとして分類されます。したがって、Javaには複数の継承があります。それだけで安全です。
継承されたクラスが同じ機能を持っている場合、実際には多重継承が複雑になります。つまり、コンパイラーは混乱を招くことになります(ダイヤモンドの問題)。したがって、Javaにより、複雑さが取り除かれ、多重継承のような機能を得るためのインターフェースが提供されました。インターフェースを使用できます
Javaには概念、つまりポリモーフィズムがあります。 Javaには2種類のポリモーフィズムがあります。メソッドのオーバーロードとメソッドのオーバーライドがあります。その中でも、メソッドのオーバーライドはスーパークラスとサブクラスの関係で発生します。サブクラスのオブジェクトを作成してスーパークラスのメソッドを呼び出し、サブクラスが複数のクラスを拡張する場合、どのスーパークラスメソッドを呼び出す必要がありますか?
または、super()
によってスーパークラスコンストラクターを呼び出しているときに、どのスーパークラスコンストラクターが呼び出されますか?
この決定は、現在のJava API機能によって不可能です。したがって、Javaでは多重継承は許可されません。
多重継承はJavaで直接許可されていませんが、インターフェースを介して許可されています。
理由:
Multiple Inheritance:複雑さとあいまいさを導入します。
インターフェース:インターフェースは、Javaの完全に抽象的なクラスです。これにより、公開されているインターフェースからプログラムの構造または内部動作を適切に描写する統一的な方法を提供します。その結果、柔軟性と再利用可能なコードが大幅に増え、他のクラスの作成方法や操作方法をより細かく制御できるようになります。
より正確には、これらはJavaの特別な構成体であり、複数のクラスにアップキャストできるクラスなど、複数の継承の種類を実行できる追加の特性を備えています。
簡単な例を見てみましょう。
同じメソッド名で機能が異なる2つのスーパークラスクラスAとBがあるとします。 (拡張)キーワードを使用した次のコードでは、多重継承はできません。
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
}
}
しかし、インターフェースを介して、(実装)キーワードを使用すると、多重継承が可能です。
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
}
}
誰が許可されないのかを正確に教えてもらえますか?
このドキュメントから答えを見つけることができます link
Javaプログラミング言語が複数のクラスを拡張することを許可しない理由の1つは、複数のクラスからフィールドを継承する機能である状態の多重継承の問題を回避するためです。
多重継承が許可されている場合、そのクラスをインスタンス化してオブジェクトを作成すると、そのオブジェクトはクラスのすべてのスーパークラスからフィールドを継承します。 2つの問題が発生します。
異なるスーパークラスのメソッドまたはコンストラクターが同じフィールドをインスタンス化するとどうなりますか?
どのメソッドまたはコンストラクターが優先されますか?
状態の多重継承が許可されたとしても、まだ実装できます
タイプの多重継承:クラスが複数のインターフェースを実装する能力。
実装の複数の継承(インターフェースのデフォルトメソッドを使用):複数のクラスからメソッド定義を継承する機能
詳細については、関連するSEの質問を参照してください。
C++では、クラスは多重継承と呼ばれる複数のクラスから(直接的または間接的に)継承できます。
ただし、C#およびJavaは、クラスをsingle inheritanceに制限します。各クラスは単一の親クラスから継承します。
多重継承は、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
から派生するため、本質的に2つのコピーflag
の2つのインスタンスがA
のクラス階層にあるため、D
の使用可能です。どちらを設定しますか?コンパイラは、flag
のD
への参照がambiguousであると文句を言います。 1つの解決策は、参照を明示的に明確にすることです。
B::flag = nflag;
別の修正方法は、BとCをvirtual base classes
として宣言することです。これは、Aの1つのコピーのみが階層に存在でき、あいまいさを排除することを意味します。
派生オブジェクトの構築時に基本クラスが初期化される順序や、派生クラスからメンバーを不注意に非表示にする方法など、他の複雑な要素には多重継承が存在します。これらの複雑さを回避するために、一部の言語は、単純な単一継承モデルに制限されています。
これにより継承が大幅に簡素化されますが、共通の祖先を持つクラスのみが動作を共有できるため、その有用性も制限されます。インターフェイスは、コードを共有することで実装されていなくても、異なる階層のクラスが共通のインターフェイスを公開できるようにすることで、この制限をある程度緩和します。