JavaRush /Java Blog /Random EN /Multiple inheritance in Java. Comparison of composition a...
HonyaSaar
Level 26
Москва

Multiple inheritance in Java. Comparison of composition and inheritance

Published in the Random EN group
Some time ago I wrote several posts about inheritance, interfaces and composition in Java. In this article, we'll look at multiple inheritance and then learn about the benefits of composition over inheritance.
Multiple inheritance in Java.  Comparison of composition and inheritance - 1

Multiple Inheritance in Java

Multiple inheritance is the ability to create classes with multiple parent classes. Unlike other popular object-oriented languages ​​such as C++, Java does not support multiple class inheritance. He does not support it because of the likelihood of encountering the “diamond problem” and instead prefers to provide some kind of comprehensive approach to solve it, using the best options that we can achieve a similar inheritance result.

"The Diamond Problem"

To understand the diamond problem more simply, let's assume that multiple inheritance is supported in Java. In this case, we can get classes with the hierarchy shown in the figure below. diamond class hierarchyLet's assume that SuperClassis an abstract class that describes a certain method, and the classes ClassAand ClassBare real classes. 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(){
    }
}
Now, let's assume that the class ClassCinherits from ClassAand ClassBat the same time, and at the same time has the following implementation:
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
    public void test(){
        //вызов метода родительского класса
        doSomething();
    }
}
Note that the method test()calls a method doSomething()of the parent class, which will lead to ambiguity because the compiler does not know which superclass method should be called. Due to the shape of the class inheritance diagram in this situation, which resembles the outline of a faceted diamond, the problem is called the “Diamond Problem”. This is the main reason why Java does not support multiple class inheritance. Note that this problem with multiple class inheritance can also occur with three classes that have at least one common method.

Multiple inheritance and interfaces

You may have noticed that I always say "multiple inheritance is not supported between classes", but it is supported between interfaces. A simple example is shown below: InterfaceA.java
package com.journaldev.inheritance;
public interface InterfaceA {

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

public interface InterfaceB {

    public void doSomething();
}
Notice that both interfaces have a method with the same name. Now let's say we have an interface that inherits from both interfaces. InterfaceC.java
package com.journaldev.inheritance;

public interface InterfaceC extends InterfaceA, InterfaceB {

    //метод, с тем же названием описан в  InterfaceA и InterfaceB
    public void doSomething();
Here, everything is ideal, since interfaces are only a reservation/description of a method, and the implementation of the method itself will be in the concrete class that implements these interfaces, so there is no possibility of encountering ambiguity with multiple inheritance of interfaces. This is why classes in Java can inherit from multiple interfaces. Let's show it with the example below. 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();
    }
}
You may have noticed that every time I override a method described in a superclass or in an interface, I use the @Override annotation. This is one of the three built-in Java annotations and you should always use it when overriding methods.

Composition as salvation

So what if we want to use a methodA()class ClassAand methodB()class function ClassBin ClassС? A solution to this could be composition - a rewritten version ClassCthat implements both class methods ClassAand ClassBalso has an implementation doSomething()for one of the objects. 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();
    }
}

Composition or inheritance?

It is good Java programming practice to take advantage of composition over inheritance. We will look at some aspects in favor of this approach.
  1. Suppose we have the following combination of parent-heir classes:

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

    The code above compiles and works fine, but what if ClassCit were implemented differently:

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

    Note that the method test()already exists in the descendant class, but returns a result of a different type. Now ClassD, in case you are using an IDE, it will not compile. You will be advised to change the return type in the descendant or superclass.

    Now let's imagine a situation where there is multi-level inheritance of classes and the superclass is not available for our changes. Now, to get rid of the compilation error, we have no other options than to change the signature or name of the subclass method. We will also have to make changes to all places where this method was called. Thus, inheritance makes our code brittle.

    The problem described above never occurs in the case of composition, and therefore makes the latter preferable to inheritance.

  2. The next problem with inheritance is that we expose all the methods of the parent to the client. And if the superclass is not designed very correctly and contains security holes. Then, even though we take full care of security in the implementation of our subclass, we will still depend on the flawed implementation of the parent class.

    Composition helps us in providing controlled access to the methods of a superclass, whereas inheritance does not maintain any control over its methods. This is also one of the main advantages of composition over inheritance.

  3. Another benefit of composition is that it adds flexibility when calling methods. The implementation of the class ClassCdescribed above is not optimal and uses early binding to the called method. Minimal changes will allow us to make method calling flexible and allow late binding (binding at runtime).

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

    The program above will display:

    doSomething implementation of A
    doSomething implementation of B

    This flexibility in method calling is not seen with inheritance, which makes composition the best approach.

  4. Unit testing is easier in the case of composition because we know that for all the methods that are used in the superclass we can stub the tests, while in inheritance we depend heavily on the superclass and don't know how the methods of the parent class will be used. So, due to inheritance, we will have to test all the methods of the superclass, which is unnecessary work.

    Ideally, inheritance should only be used when the “ is-a ” relationship is true for the parent and child classes, otherwise composition should be preferred.

Original article
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION