JavaRush /Java Blog /Random-JA /Java における多重継承。構成と継承の比較
HonyaSaar
レベル 26
Москва

Java における多重継承。構成と継承の比較

Random-JA グループに公開済み
少し前に、私は Java の継承、インターフェース、構成についていくつかの記事を書きました。この記事では、多重継承について見てから、継承に対する合成の利点について学びます。
Java における多重継承。 構成と継承の比較 - 1

Java での多重継承

多重継承とは、複数の親クラスを持つクラスを作成する機能です。C++ などの他の一般的なオブジェクト指向言語とは異なり、Java は複数のクラスの継承をサポートしません。彼は、「ダイヤモンド問題」に遭遇する可能性があるため、この問題を支持せず、その代わりに、同様の相続結果を達成できる最善の選択肢を使用して、問題を解決するための何らかの包括的なアプローチを提供することを好みます。

「ダイヤモンド問題」

ダイヤモンドの問題をより簡単に理解するために、Java で多重継承がサポートされていると仮定してみましょう。この場合、下図のような階層を持つクラスが取得できます。 が特定のメソッドを記述する抽象クラスであり、クラスと が実際のクラスであると ダイヤモンドのクラス階層仮定しましょう。 SuperClass ClassA ClassB SuperClass.java
package com.journaldev.inheritance;
public abstract class SuperClass {
   	public abstract void doSomething();
}
ClassA.java
package com.journaldev.inheritance;
public class ClassA extends SuperClass{
    @Override
 public void doSomething(){
        System.out.println("Какая-то реализация класса A");
    }
  //собственный метод класса  ClassA
    public void methodA(){
    }
}
ここで、クラスが次の実装を継承し、同時に次の実装を持っていると仮定し ClassCます。 ClassA ClassB
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
    public void test(){
        //вызов метода родительского класса
        doSomething();
    }
}
このメソッドは親クラスの test()メソッドを呼び出します doSomething()が、コンパイラはどのスーパークラス メソッドを呼び出す必要があるかわからないため、あいまいさが生じることに注意してください。この状況におけるクラス継承図の形状が、ファセットカットされたダイヤモンドの輪郭に似ているため、この問題は「ダイヤモンド問題」と呼ばれます。これが、Java が複数のクラスの継承をサポートしない主な理由です。複数クラスの継承に関するこの問題は、少なくとも 1 つの共通メソッドを持つ 3 つのクラスでも発生する可能性があることに注意してください。

複数の継承とインターフェース

お気づきかもしれませんが、私は常に「クラス間では多重継承はサポートされていない」と述べていますが、インターフェイス間ではサポートされています。簡単な例を以下に示します。 InterfaceA.java
package com.journaldev.inheritance;
public interface InterfaceA {

    public void doSomething();
}
InterfaceB.java
package com.journaldev.inheritance;

public interface InterfaceB {

    public void doSomething();
}
両方のインターフェイスに同じ名前のメソッドがあることに注意してください。ここで、両方のインターフェイスを継承するインターフェイスがあるとします。 InterfaceC.java
package com.journaldev.inheritance;

public interface InterfaceC extends InterfaceA, InterfaceB {

    //метод, с тем же названием описан в  InterfaceA и InterfaceB
    public void doSomething();
ここではすべてが理想的です。インターフェイスはメソッドの予約/説明にすぎず、メソッド自体の実装はこれらのインターフェイスを実装する具象クラス内にあるため、インターフェイスの多重継承であいまいさが発生する可能性はありません。これが、Java のクラスが複数のインターフェイスから継承できる理由です。以下の例でそれを示してみましょう。 InterfacesImpl.java
package com.journaldev.inheritance;

public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {

    @Override
    public void doSomething() {
        System.out.println("doSomething реализация реального класса ");
    }

    public static void main(String[] args) {
        InterfaceA objA = new InterfacesImpl();
        InterfaceB objB = new InterfacesImpl();
        InterfaceC objC = new InterfacesImpl();

        //все вызываемые ниже методы получат одинаковую реализацию конкретного класса

        objA.doSomething();
        objB.doSomething();
        objC.doSomething();
    }
}
スーパークラスまたはインターフェイスで記述されたメソッドをオーバーライドするたびに、 @Override アノテーションを使用していることに気づいたかもしれません。これは 3 つの組み込み Java アノテーションの 1 つであり、メソッドをオーバーライドするときは常にこれを使用する必要があります。

救いとしての構成

methodA()では、クラス ClassAmethodB()クラス関数 ClassBを使用したい場合はどうすればよいでしょうか ClassС? これに対する解決策は、 ClassC両方のクラスメソッドを実装し ClassA、オブジェクトの 1 つの ClassB実装も持つ書き直されたバージョンである合成です。 doSomething() ClassC.java
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. 次のような親クラスと継承クラスの組み合わせがあるとします。

    ClassC.java

    package com.journaldev.inheritance;
    
    public class ClassC{
    
    public void methodC(){
      	}
    
    }

    ClassD.java

    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()子孫クラスにすでに存在しますが、異なる型の結果を返すことに注意してください。ClassDIDE を使用している場合、IDE はコンパイルされません。子孫またはスーパークラスの戻り値の型を変更することをお勧めします。

    ここで、クラスのマルチレベル継承があり、スーパークラスを変更に使用できない状況を想像してみましょう。ここで、コンパイル エラーを取り除くには、サブクラス メソッドのシグネチャまたは名前を変更する以外に選択肢はありません。また、このメソッドが呼び出されたすべての場所を変更する必要があります。したがって、継承によりコードが脆弱になります。

    合成の場合、上記の問題は決して発生しないため、合成の方が継承よりも好ましいと言えます。

  2. 継承に関する次の問題は、親のメソッドをすべてクライアントに公開してしまうことです。また、スーパークラスが適切に設計されておらず、セキュリティ ホールが含まれている場合も同様です。その場合、サブクラスの実装ではセキュリティに十分な注意を払っていても、依然として親クラスの欠陥のある実装に依存することになります。

    合成は、スーパークラスのメソッドへの制御されたアクセスを提供するのに役立ちますが、継承はそのメソッドに対する制御を維持しません。これは、継承に対する合成の主な利点の 1 つでもあります。

  3. 合成のもう 1 つの利点は、メソッドを呼び出すときに柔軟性が高まることです。上記のクラスの実装はClassC最適ではなく、呼び出されたメソッドへの早期バインディングを使用します。最小限の変更により、メソッド呼び出しを柔軟にし、遅延バインディング (実行時のバインディング) が可能になります。

    ClassC.java

    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

    メソッド呼び出しにおけるこの柔軟性は継承では見られないため、合成が最良のアプローチとなります。

  4. 合成の場合は、スーパークラスで使用されるすべてのメソッドについてテストをスタブできることがわかっているため、単体テストが容易になりますが、継承の場合はスーパークラスに大きく依存し、親クラスのメソッドがどのように機能するかがわかりません。使用されます。したがって、継承により、スーパークラスのすべてのメソッドをテストする必要がありますが、これは不必要な作業です。

    理想的には、親クラスと子クラスに対して「 is-a 」関係が trueの場合にのみ継承を使用し、それ以外の場合は合成を優先する必要があります。

原著
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION