Поиск по сайту:

Множественное наследование в Java


Сегодня мы рассмотрим множественное наследование в Java. Когда-то я написал несколько постов о композиции в java. В этом посте мы рассмотрим множественное наследование в Java, а затем сравним композицию и наследование.

Множественное наследование в Java

Алмазная проблема в Java

package com.journaldev.inheritance;

public abstract class SuperClass {

	public abstract void doSomething();
}

КлассA.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(){
		
	}
}

КлассB.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(){
		
	}
}

Теперь предположим, что реализация ClassC будет примерно такой, как показано ниже, и она расширяет как ClassA, так и ClassB. КлассC.java

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

}

Обратите внимание, что метод test() вызывает метод суперкласса doSomething(). Это приводит к неоднозначности, поскольку компилятор не знает, какой метод суперкласса выполнять. Из-за ромбовидной диаграммы классов в java она называется Diamond Problem. Алмазная проблема в Java — основная причина того, что Java не поддерживает множественное наследование в классах. Обратите внимание, что описанная выше проблема с множественным наследованием классов также может возникнуть только с тремя классами, каждый из которых имеет хотя бы один общий метод.

Множественное наследование в интерфейсах Java

Вы могли заметить, что я всегда говорю, что множественное наследование не поддерживается в классах, но поддерживается в интерфейсах. Один интерфейс может расширять несколько интерфейсов, ниже приведен простой пример. ИнтерфейсA.java

package com.journaldev.inheritance;

public interface InterfaceA {

	public void doSomething();
}

ИнтерфейсB.java

package com.journaldev.inheritance;

public interface InterfaceB {

	public void doSomething();
}

Обратите внимание, что оба интерфейса объявляют один и тот же метод, теперь у нас может быть интерфейс, расширяющий оба этих интерфейса, как показано ниже. InterfaceC.java

package com.journaldev.inheritance;

public interface InterfaceC extends InterfaceA, InterfaceB {

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

Это совершенно нормально, потому что интерфейсы только объявляют методы, а фактическая реализация будет выполняться конкретными классами, реализующими интерфейсы. Таким образом, нет возможности какой-либо двусмысленности при множественном наследовании в интерфейсах Java. Вот почему класс Java может реализовывать несколько интерфейсов, как в примере ниже. InterfacesImpl.java

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

}

Вы заметили, что каждый раз, когда я переопределяю любой метод суперкласса или реализую любой метод интерфейса, я всегда использую аннотацию переопределения при переопределении любого метода.

Состав для спасения

Итак, что делать, если мы хотим использовать функцию ClassA methodA() и функцию ClassB methodB() в <КлассC. Решение заключается в использовании композиции. Вот рефакторинговая версия ClassC, которая использует композицию для использования методов обоих классов, а также использует метод doSomething() из одного из объектов. КлассC.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. Suppose we have a superclass and subclass as follows: 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 above code compiles and works fine but what if ClassC implementation is changed like below: ClassC.java

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

    Notice that test() method already exists in the subclass but the return type is different. Now the ClassD won’t compile and if you are using any IDE, it will suggest you change the return type in either superclass or subclass. Now imagine the situation where we have multiple levels of class inheritance and superclass is not controlled by us. We will have no choice but to change our subclass method signature or its name to remove the compilation error. Also, we will have to make a change in all the places where our subclass method was getting invoked, so inheritance makes our code fragile. The above problem will never occur with composition and that makes it more favorable over inheritance.

  2. Another problem with inheritance is that we are exposing all the superclass methods to the client and if our superclass is not properly designed and there are security holes, then even though we take complete care in implementing our class, we get affected by the poor implementation of the superclass. Composition helps us in providing controlled access to the superclass methods whereas inheritance doesn’t provide any control of the superclass methods, this is also one of the major advantages of composition over inheritance.

  3. Another benefit with composition is that it provides flexibility in the invocation of methods. Our above implementation of ClassC is not optimal and provides compile-time binding with the method that will be invoked, with minimal change we can make the method invocation flexible and make it dynamic. 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();
    	}
    }
    

    Output of above program is:

    doSomething implementation of A
    doSomething implementation of B
    

    This flexibility in method invocation is not available in inheritance and boosts the best practice to favor composition over inheritance.

  4. Unit testing is easy in composition because we know what all methods we are using from superclass and we can mock it up for testing whereas in inheritance we depend heavily on superclass and don’t know what all methods of superclass will be used, so we need to test all the methods of superclass, that is an extra work and we need to do it unnecessarily because of inheritance.

Это все, что касается множественного наследования в Java и краткого рассмотрения композиции.