Last Updated:

Multiple Inheritance in Java and Composition vs Inheritance

I recently wrote several posts about inheritance and composition in Java. In this article, we'll explore multiple inheritances and then learn about the benefits of composition over inheritance.

Multiple Inheritance in Java

Multiple inheritance is the ability to create a single class with multiple superclasses.

Unlike some other popular object-oriented programming languages such as C++, Java does not provide support for multiple inheritance in classes. Java does not support multiple class inheritance because it can lead to a rhombus problem (diamond-shaped inheritance) and instead of providing a complex way to resolve this problem, they came up with a better way.

Diamond problem

Understanding the problem with diamonds is easy: let's assume that multiple inheritance was implemented in Java. In this case, we could have a hierarchy of classes, as in the image below.

 

Multiple Inheritance in Java

 

Let's create an abstract SuperClass superclass with the doSomething() method, as well as two ClassA classes, ClassB

SuperClass.java

ClassA.java

ClassB.java

now let's create a classc that inherits classa and classb

Notice that the method calls the superclass method test() doSomething()

This leads to uncertainty: the compiler does not know which superclass method to execute because of the diamond-shaped shape (above in the class diagram). This is called the rhombus problem— and it's the main reason why Java doesn't support multiple class inheritance.

Multiple inheritance in interfaces

You may have noticed, I always say that multiple inheritance is not supported in classes, but it is supported in interfaces and a single interface can inherit multiple interfaces, below is a simple example.

InterfaceA.java

InterfaceB.java

Note that the same method is declared in both interfaces, and now let's see what happens:

InterfaceC.java

And that's a great way out, because interfaces only declare methods, and the actual implementation will be done in the specific classes that implement interfaces, so there's no way to ambiguously interpret multiple inheritance in an interface.

Now let's take a look at the code below:

InterfacesImpl.java

package.com.p-qc.inheritance;
public class InterfacesImpl implementations InterfaceA, InterfaceB, InterfaceC {
@Override
public void doSomething() {
System.out.println("doSomething is implemented in a concrete class");
}
public static void main(String[] args) {
InterfaceA objA = new InterfacesImpl();
InterfaceB objB = new InterfacesImpl();
InterfaceC objC = new InterfacesImpl();

//calling methods with concrete implementation
objA.doSomething();
objB.doSomething();
objC.doSomething();
}
}

And how do you use Composition here?

So what if we want to use the methodA() method of the class and the methodB() method of the class in the solution is to use composition. The following is a version of the ClassC class using composition:ClassAClassBClassC?

ClassC.java

So what to use: Composition or Inheritance?

One of the best practices in Java programming is "Use composition more often than inheritance." Let's take a look at this approach:

  • Suppose we have a superclass and a subclass:

ClassC.java

ClassD.java

The code above compiles and works fine, but what happens if the implementation of the ClassC class changes as shown below:

Note that the test() method already exists in the subclass, but the type of the return is different, now The ClassD will not compile, and if you are using any IDE, you will be prompted to change the return type to the type of superclass or subclass.

Now imagine a situation where we have several levels of class inheritance, but the superclass is not controlled by us. In this case, we will have no choice but to change the method signature of our subclass or its name to remove the compilation error. We also need to make changes to all the places where our subclass method was used.

 

This problem will never happen to the composition, so it makes it preferable to inheritance.

  • Another problem with inheritance is that we expose all the superclass methods to the client, and if our superclass is not properly designed and there are security holes, then even if we take care of the correct implementation of our subclass, we still get the problems that we got from the superclass.
    Composition helps us control access to superclass methods, while inheritance provides no control over superclass methods. This is also one of the main advantages of composition over inheritance in Java.
  • Another advantage of composition is that it provides flexibility in calling methods. Below is a good example of using composition:

ClassC.java

The result of this program:

This flexibility in calling methods is not available in inheritance.

  • When using composition, it's easy to do unit testing because we know that all methods are superclass-independent. While in inheritance, we are largely dependent on the superclass and do not know which methods will be used, so we have to check all the methods of the superclass. And this is additional work that no one needs.

Ideally, we should only use inheritance when the "is-a" ratio is valid for superclass and subclass in all cases, otherwise we should use composition.