Java での多重継承
多重継承を使用すると、複数のスーパークラスを継承するクラスを作成できます。C++ などの他の一般的なオブジェクト指向プログラミング言語とは異なり、Java ではクラスからの多重継承が許可されていません。Java は、ダイヤモンド問題を引き起こす可能性があるため、複数のクラスの継承をサポートしていません。そして、この問題を解決する方法を探すのではなく、多重継承のような同じ結果を達成する方法を考えるより良い選択肢があります。ダイヤモンド問題
ダイヤモンドの問題をより簡単に理解するために、Java で多重継承がサポートされていると仮定してみましょう。この場合、以下の図に示すようなクラス階層を持つことができます。 クラスがSuperClass
抽象クラスであり、その中で何らかのメソッドが宣言されていると仮定します。具象クラスClassA
と の両方ClassB
。
package com.journaldev.inheritance;
public abstract class SuperClass {
public abstract void doSomething();
}
package com.journaldev.inheritance;
public class ClassA extends SuperClass{
@Override
public void doSomething(){
System.out.println("doSomething implementation of A");
}
//ClassA own method
public void methodA(){
}
}
package com.journaldev.inheritance;
public class ClassB extends SuperClass{
@Override
public void doSomething(){
System.out.println("doSomething implementation of B");
}
//ClassB specific method
public void methodB(){
}
}
ClassC
次に、それを実装し、ClassA
とから継承 したいとしますClassB
。
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
public void test(){
//calling super class method
doSomething();
}
}
このメソッドはtest()
スーパークラス メソッドを呼び出すことに注意してくださいdoSomething()
。コンパイラはどのスーパークラス メソッドを実行するかわからないため、あいまいさが生じます。これはダイヤモンド問題と呼ばれるダイヤモンド型のクラス図です。これが、Java が多重継承をサポートしない主な理由です。複数クラスの継承に関する上記の問題は、少なくとも 1 つの共通メソッドを持つ 3 つのクラスでのみ発生する可能性があることに注意してください。
複数のインターフェースの継承
Java では、多重継承はクラスではサポートされていませんが、インターフェイスではサポートされています。また、1 つのインターフェイスで他の多くのインターフェイスを拡張できます。以下に簡単な例を示します。package com.journaldev.inheritance;
public interface InterfaceA {
public void doSomething();
}
package com.journaldev.inheritance;
public interface InterfaceB {
public void doSomething();
}
両方のインターフェイスが同じメソッドを宣言していることに注意してください。ここで、以下の例に示すように、これらのインターフェイスの両方を拡張するインターフェイスを作成できます。
package com.journaldev.inheritance;
public interface InterfaceC extends InterfaceA, InterfaceB {
//same method is declared in InterfaceA and InterfaceB both
public void doSomething();
}
インターフェイスはメソッドを宣言するだけで、実装はインターフェイスを継承するクラスで行われるため、これはうまく機能します。したがって、複数のインターフェイスの継承においてあいまいさが生じることはありません。
package com.journaldev.inheritance;
public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {
@Override
public void doSomething() {
System.out.println("doSomething implementation of concrete class");
}
public static void main(String[] args) {
InterfaceA objA = new InterfacesImpl();
InterfaceB objB = new InterfacesImpl();
InterfaceC objC = new InterfacesImpl();
//all the method calls below are going to same concrete implementation
objA.doSomething();
objB.doSomething();
objC.doSomething();
}
}
スーパークラス メソッドをオーバーライドする場合、またはインターフェイス メソッドを実装する場合は常に、アノテーションを使用することに注意してください@Override
。methodA()
クラスの関数とクラスのClassA
関数をクラス内で使用したい場合はどうすればよいでしょうか? 解決策は合成の使用にあります。以下は、合成を使用してクラス メソッドとオブジェクトの 1 つのメソッドの両方を定義するクラスのバージョンです。 methodB()
ClassB
ClassC
ClassC
doSomething()
package com.journaldev.inheritance;
public class ClassC{
ClassA objA = new ClassA();
ClassB objB = new ClassB();
public void test(){
objA.doSomething();
}
public void methodA(){
objA.methodA();
}
public void methodB(){
objB.methodB();
}
}
構成と継承
Java プログラミングの最良の実践方法の 1 つは、「継承前に構成を承認する」ことです。このアプローチを支持するいくつかの側面を検討します。-
スーパークラスとそれを拡張するクラスがあるとします。
package com.journaldev.inheritance; public class ClassC{ public void methodC(){ } } package com.journaldev.inheritance; public class ClassD extends ClassC{ public int test(){ return 0; } }
上記のコードはコンパイルされ、正常に動作します。しかし、クラスの実装を
ClassC
以下のように変更するとどうなるでしょうか。package com.journaldev.inheritance; public class ClassC{ public void methodC(){ } public void test(){ } }
このメソッドは
test()
サブクラスにすでに存在しますが、戻り値の型が異なることに注意してください。これでクラスはClassD
コンパイルされなくなり、IDE を使用すると、スーパークラスまたはサブクラスの戻り値の型を変更するように求められます。次に、マルチレベルのクラス継承階層があり、スーパークラスにアクセスできない状況を想像してください。コンパイル エラーを削除するには、サブクラス メソッドのシグネチャまたはその名前を変更する以外に選択肢はありません。また、サブクラス メソッドが呼び出されるすべての場所でサブクラス メソッドを変更する必要があります。したがって、継承によりコードが脆弱になります。
上記の問題は合成では決して起こらないため、継承がより魅力的になります。
-
継承に関するもう 1 つの問題は、スーパークラスのすべてのメソッドをクライアントに公開しており、スーパークラスが適切に設計されておらず、セキュリティ ホールがある場合、クラスの最良の実装を実装したとしても、不適切な実装の影響を受けることです。スーパークラスの 。合成はスーパークラス メソッドへの制御されたアクセスを提供するのに役立ちますが、継承はスーパークラス メソッドに対する制御を提供しません。これは、継承による合成の主な利点の 1 つでもあります。
-
合成のもう 1 つの利点は、メソッドの呼び出しに柔軟性が与えられることです。上記のクラスの実装は
ClassC
最適ではなく、コンパイル時間が呼び出されるメソッドに依存することになります。最小限の変更で、メソッド呼び出しを柔軟かつ動的にすることができます。package com.journaldev.inheritance; public class ClassC{ SuperClass obj = null; public ClassC(SuperClass o){ this.obj = o; } public void test(){ obj.doSomething(); } public static void main(String args[]){ ClassC obj1 = new ClassC(new ClassA()); ClassC obj2 = new ClassC(new ClassB()); obj1.test(); obj2.test(); } }
上記のプログラムの結果:
doSomething implementation of A doSomething implementation of B
メソッド呼び出しにおけるこの柔軟性は、継承では利用できないため、構成の選択に別の利点が追加されます。
-
スーパークラスのすべてのメソッドを使用していることがわかっており、それらをテスト用にコピーできるため、合成を使用すると単体テストが簡単になります。一方、継承ではスーパークラスに依存することが多く、使用されるスーパークラスのメソッドをすべて知っているわけではありません。したがって、スーパークラスのすべてのメソッドをテストする必要がありますが、継承により余分な作業が必要になります。
理想的には、サブクラスとスーパークラスの関係が「である」と定義されている場合にのみ継承を使用する必要があります。それ以外の場合は、コンポジションを使用することをお勧めします。
GO TO FULL VERSION