- Multiple Inheritance of State, Implementation, and Type
- Множественное наследование в Java. Сравнение композиции и наследования
- Множественное наследование в Java
- «Проблема Алмаза»
- Множественное наследование и интерфейсы
- Композиция как спасение
- Композиция или наследование?
- Множественное наследование в Java. Композиция в сравнении с Наследованием
- Ромбовидная проблема
- Множественное наследование Интерфейсов
- Композиция против Наследования
Multiple Inheritance of State, Implementation, and Type
One significant difference between classes and interfaces is that classes can have fields whereas interfaces cannot. In addition, you can instantiate a class to create an object, which you cannot do with interfaces. As explained in the section What Is an Object?, an object stores its state in fields, which are defined in classes. One reason why the Java programming language does not permit you to extend more than one class is to avoid the issues of multiple inheritance of state, which is the ability to inherit fields from multiple classes. For example, suppose that you are able to define a new class that extends multiple classes. When you create an object by instantiating that class, that object will inherit fields from all of the class’s superclasses. What if methods or constructors from different superclasses instantiate the same field? Which method or constructor will take precedence? Because interfaces do not contain fields, you do not have to worry about problems that result from multiple inheritance of state.
Multiple inheritance of implementation is the ability to inherit method definitions from multiple classes. Problems arise with this type of multiple inheritance, such as name conflicts and ambiguity. When compilers of programming languages that support this type of multiple inheritance encounter superclasses that contain methods with the same name, they sometimes cannot determine which member or method to access or invoke. In addition, a programmer can unwittingly introduce a name conflict by adding a new method to a superclass. Default methods introduce one form of multiple inheritance of implementation. A class can implement more than one interface, which can contain default methods that have the same name. The Java compiler provides some rules to determine which default method a particular class uses.
The Java programming language supports multiple inheritance of type, which is the ability of a class to implement more than one interface. An object can have multiple types: the type of its own class and the types of all the interfaces that the class implements. This means that if a variable is declared to be the type of an interface, then its value can reference any object that is instantiated from any class that implements the interface. This is discussed in the section Using an Interface as a Type.
As with multiple inheritance of implementation, a class can inherit different implementations of a method defined (as default or static) in the interfaces that it extends. In this case, the compiler or the user must decide which one to use.
Множественное наследование в Java. Сравнение композиции и наследования
Некоторое время назад я написал несколько постов о наследовании, интерфейсах и композиции в Java. В этой статье мы рассмотрим множественное наследование, а затем узнаем о преимуществах композиции перед наследованием.
Множественное наследование в Java
Множественное наследование – способность создавать классы с множеством классов-родителей. В отличии от других популярных объектно-ориентированных языков, вроде С++, язык Java не поддерживает множественное наследование классов. Не поддерживает он его из-за вероятности столкнуться с «проблемой алмаза» и вместо этого предпочитает обеспечивать некий комплексный подход для его решения, используя лучшие варианты из тех, которыми мы можем достичь аналогичный результат наследования.
«Проблема Алмаза»
Для более простого понимания проблемы алмаза допустим, что множественное наследование поддерживается в Java. В этом случае, мы можем получить классы с иерархией показанной на рисунке ниже. Предположим ,что SuperClass — это абстрактный класс, описывающий некоторый метод, а классы ClassA и ClassB — реальные классы. SuperClass.java
package com.journaldev.inheritance; public abstract class SuperClass
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 нет поддержки множественного наследования классов. Отметим, что указанная проблема с множественным наследованием классов также может возникнуть с тремя классами, имеющими как минимум один общий метод.
Множественное наследование и интерфейсы
Вы могли обратить внимание, что я всегда говорю: «множественное наследование не поддерживается между классами», но оно поддерживается между интерфейсами. Ниже показан простой пример: InterfaceA.java
package com.journaldev.inheritance; public interface InterfaceA
package com.journaldev.inheritance; public interface InterfaceB
Заметьте, что оба интерфейса, имеют метод с одинаковым названием. Теперь, допустим, у нас есть интерфейс, унаследованный от обоих интерфейсов. 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. Это одна из трех встроенных Java-аннотаций и вам следует всегда использовать ее при переопределении методов.
Композиция как спасение
Так что же делать, если мы хотим использовать функцию methodA() класса ClassA и methodB() класса ClassB в ClassС ? Решением этого может стать композиция – переписанная версия ClassC реализующая оба метода классов ClassA и 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(); >>
Композиция или наследование?
package com.journaldev.inheritance; public class ClassC < public void methodC()< >>
package com.journaldev.inheritance; public class ClassD extends ClassC < public int test()< return 0; >>
package com.journaldev.inheritance; public class ClassC < public void methodC()< >public void test() < >>
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
Множественное наследование в Java. Композиция в сравнении с Наследованием
Множественное наследование дает возможность создать класс, наследованный от нескольких суперклассов. В отличии от некоторых других популярных объектно-ориентированных языков программирования, таких как С++ в Java запрещено множественное наследование от классов. Java не поддерживает множественное наследование классов потому, что это может привести к ромбовидной проблеме. И вместо того, чтобы искать способы решения этой проблемы, есть лучшие варианты, как мы можем добиться того же самого результата как множественное наследование.
Ромбовидная проблема
Для более легкого понимания ромбовидной проблемы, давайте предположим, что множественное наследование поддерживается в Java. В этом случае, у нас могла бы быть иерархия классов как показано на изображении ниже. Предположим, что класс SuperClass является абстрактным и в нем объявлен некоторый метод. И конкретные классы ClassA и ClassB .
package com.journaldev.inheritance; public abstract class SuperClass < public abstract void doSomething(); >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() < >> 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() < >>
package com.journaldev.inheritance; public class ClassC extends ClassA, ClassB < public void test()< //calling super class method doSomething(); >>
Обратите внимание, что метод test() вызывает метод суперкласса doSomething() . Это приводит к неоднозначности, поскольку компилятор не знает, какой метод суперкласса выполнять. Это есть диаграмма классов ромбовидной формы, которая называется ромбовидная проблема. Это есть главная причина, по которой в Java не поддерживается множественное наследование. Заметьте, что вышеупомянутая проблема с многократным наследованием классов может произойти только с тремя классами, у которых есть хоть один общий метод.
Множественное наследование Интерфейсов
В Java множественное наследование не поддерживается в классах, но оно поддерживается в интерфейсах. И один интерфейс может расширять множество других интерфейсов. Ниже представлен простой пример.
package com.journaldev.inheritance; public interface InterfaceA < public void doSomething(); >package com.journaldev.inheritance; public interface InterfaceB
Обратите внимание, что оба интерфейса объявляют один и тот же самый метод. Теперь мы можем создать интерфейс, расширяющий оба эти интерфейса, как представлено в примере ниже.
package com.journaldev.inheritance; public interface InterfaceC extends InterfaceA, InterfaceB < //same method is declared in InterfaceA and InterfaceB both public void doSomething(); >
Это отлично работает, потому что интерфейсы только объявляют методы, а реализация будет выполнена в классах, наследующих интерфейс. Таким образом нет никакой возможности получить неоднозначность во множественном наследовании интерфейсов.
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(); >>
Обратите внимание, что каждый раз, переопределяя любой метод суперкласса или реализуя метод интерфейса используйте аннотацию @Override . Что делать, если мы хотим использовать функцию methodA() , класса ClassA и функцию methodB() , класса ClassB в классе ClassC ? Решение находится в использовании композиции. Ниже представлена версия класса ClassC , которая использует композицию, чтобы определить оба метода классов и метод doSomething() , одного из объектов.
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(); >>
Композиция против Наследования
package com.journaldev.inheritance; public class ClassC < public void methodC()< >> 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() < >>
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