Multiple Inheritance in Java

Multiple Inheritance in Java ist die Fähigkeit, eine einzelne Klasse mit mehreren Superklassen zu erstellen. Im Gegensatz zu einigen anderen beliebten objektorientierten Programmiersprachen wie C++, bietet Java keine Unterstützung für Multiple Inheritance in Klassen. Java unterstützt Multiple Inheritance in Klassen nicht, da dies zu Diamond-Problemen führen kann, und anstatt eine komplexe Lösung dafür zu bieten, gibt es bessere Wege, um dasselbe Ergebnis wie bei Multiple Inheritance zu erreichen.

Diamond-Problem in Java

Um das Diamond-Problem leicht zu verstehen, nehmen wir an, dass Multiple Inheritance in Java unterstützt würde. In diesem Fall könnten wir eine Klassenhierarchie wie im Bild unten haben.

Lassen Sie uns sagen, SuperClass ist eine abstrakte Klasse, die eine Methode deklariert, und ClassA, ClassB sind konkrete Klassen.

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("doSomething implementation of A");
    }
    
    //ClassA own method
    public void methodA(){
        
    }
}

ClassB.java

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(){
        
    }
}

Jetzt sagen wir, die Implementierung von ClassC wäre so, wie unten beschrieben, und sie erweitert sowohl ClassA als auch ClassB.

package com.journaldev.inheritance;

// this is just an assumption to explain the diamond problem
//this code won't compile
public class ClassC extends ClassA, ClassB{

    public void test(){
        //calling super class method
        doSomething();
    }

}

Beachten Sie, dass die test() Methode einen Aufruf an die Superklassenmethode doSomething() macht. Dies führt zu einer Mehrdeutigkeit, da der Compiler nicht weiß, welche Superklassenmethode ausgeführt werden soll. Aufgrund des diamantförmigen Klassendiagramms wird dies als Diamond-Problem in Java bezeichnet. Das Diamond-Problem in Java ist der Hauptgrund, warum Java Multiple Inheritance in Klassen nicht unterstützt. Beachten Sie, dass das oben genannte Problem mit der Mehrfachvererbung von Klassen auch nur mit drei Klassen auftreten kann, bei denen alle mindestens eine gemeinsame Methode haben.

Multiple Inheritance in Java Interfaces

Sie haben vielleicht bemerkt, dass ich immer sage, dass Multiple Inheritance in Klassen nicht unterstützt wird, aber in Interfaces unterstützt wird. Ein einzelnes Interface kann mehrere Interfaces erweitern, unten ist ein einfaches Beispiel.

package com.journaldev.inheritance;

public interface InterfaceA {

    public void doSomething();
}

InterfaceB.java

package com.journaldev.inheritance;

public interface InterfaceB {

    public void doSomething();
}

Beachten Sie, dass beide Interfaces dieselbe Methode deklarieren, jetzt können wir ein Interface haben, das beide diese Interfaces erweitert.

package com.journaldev.inheritance;

public interface InterfaceC extends InterfaceA, InterfaceB {

    //same method is declared in InterfaceA and InterfaceB both
    public void doSomething();
    
}

Dies ist völlig in Ordnung, da die Interfaces nur die Methoden deklarieren und die eigentliche Implementierung von konkreten Klassen durchgeführt wird, die die Interfaces implementieren. So gibt es keine Möglichkeit einer Mehrdeutigkeit bei Multiple Inheritance in Java Interfaces. Deshalb kann eine Java-Klasse mehrere Interfaces implementieren, wie im folgenden Beispiel.

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();
    }

}

Haben Sie bemerkt, dass ich jedes Mal, wenn ich irgendeine Superklassenmethode überschreibe oder irgendeine Interface-Methode implementiere, die @Override Annotation verwende. Override Annotation ist eine der drei eingebauten Java-Annotationen, und wir sollten immer die Override Annotation verwenden, wenn wir irgendeine Methode überschreiben.

Composition zur Rettung – Multiple Inheritance

Was also tun, wenn wir die Funktion methodA() von ClassA und die Funktion methodB() von ClassB in ClassC nutzen wollen. Die Lösung liegt in der Verwendung von Composition. Hier ist eine überarbeitete Version von ClassC, die Composition nutzt, um beide Klassenmethoden zu nutzen und auch die doSomething()-Methode von einem der Objekte zu verwenden.

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();
    }
}

Composition vs. Inheritance

Eine der besten Praktiken der Java-Programmierung ist es, „Composition gegenüber Inheritance zu bevorzugen“. Wir werden einige Aspekte betrachten, die diesen Ansatz bevorzugen.

Nehmen wir an, wir haben eine Superklasse und eine Subklasse wie folgt:

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;
    }
}

Der obige Code wird kompiliert und funktioniert gut, aber was, wenn die Implementierung von ClassC wie unten geändert wird:

package com.journaldev.inheritance;

public class ClassC{

    public void methodC(){
    }

    public void test(){
    }
}

Beachten Sie, dass die test()-Methode bereits in der Subklasse existiert, aber der Rückgabetyp ist anders. Jetzt wird ClassD nicht kompilieren, und wenn Sie eine IDE verwenden, wird sie vorschlagen, den Rückgabetyp entweder in der Superklasse oder in der Subklasse zu ändern. Stellen Sie sich jetzt die Situation vor, in der wir mehrere Ebenen der Klassenvererbung haben und die Superklasse nicht von uns kontrolliert wird. Wir haben keine andere Wahl, als die Signatur oder den Namen unserer Subklassenmethode zu ändern, um den Kompilierungsfehler zu entfernen. Auch müssten wir eine Änderung an allen Stellen vornehmen, wo unsere Subklassenmethode aufgerufen wurde, daher macht Vererbung unseren Code brüchig. Das oben genannte Problem wird nie mit Composition auftreten, und das macht es über Vererbung bevorzugbar.

Ein weiteres Problem mit Vererbung ist, dass wir alle Methoden der Superklasse dem Client aussetzen, und wenn unsere Superklasse nicht richtig entworfen ist und es Sicherheitslücken gibt, dann werden wir, auch wenn wir bei der Implementierung unserer Klasse vollständige Sorgfalt anwenden, von der schlechten Implementierung der Superklasse betroffen sein. Composition hilft uns, kontrollierten Zugang zu den Methoden der Superklasse zu bieten, während Vererbung keine Kontrolle über die Methoden der Superklasse bietet, dies ist auch einer der großen Vorteile von Composition über Vererbung.

Ein weiterer Vorteil von Composition ist, dass es Flexibilität bei der Aufruf von Methoden bietet. Unsere obige Implementierung von ClassC ist nicht optimal und bietet eine Compile-Zeit-Bindung mit der Methode, die aufgerufen werden wird, mit minimalen Änderungen können wir den Methodenaufruf flexibel gestalten und dynamisch machen.

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();
    }
}

Das Output des obigen Programms ist:

doSomething implementation of A
doSomething implementation of B

Diese Flexibilität beim Methodenaufruf ist bei Vererbung nicht verfügbar und fördert die beste Praxis, Composition über Vererbung zu bevorzugen.

Unit-Testing ist in Composition einfach, weil wir wissen, welche Methoden wir von der Superklasse verwenden und wir können sie für Tests mocken, während wir bei Vererbung stark von der Superklasse abhängig sind und nicht wissen, welche Methoden der Superklasse verwendet werden, so müssen wir alle Methoden der Superklasse testen, das ist eine zusätzliche Arbeit und wir müssen es unnötigerweise wegen Vererbung tun.

Das ist alles zu Multiple Inheritance in Java und ein kurzer Blick auf Composition.

Kostenlosen Account erstellen

Registrieren Sie sich jetzt und erhalten Sie Zugang zu unseren Cloud Produkten.

Das könnte Sie auch interessieren: