Почему в java нет множественного наследования
Перейти к содержимому

Почему в java нет множественного наследования

  • автор:

Чем в java заменить множественное наследование?

Хочу реализовать приблизительно следующую архитектуру:

Пояснение:

  • Base — Что-то вроде сущности-хранилища, в котором хранятся какие-то базовые объекты;
  • ChildN — потомки, реализующие свой спектр действий над данными, которые хранятся в классе Base . По сути являются интерфейсами, т.к. никакого состояния не хранят;
  • static Mixin — класс, объединяющий в себе все методы из ChildN . Коллизий между именами методов нет. Все его методы (т.е. методы, унаследованные от ChildN ) должны быть статическими, вызываться без инстанцирования.

В python эта задача легко бы решилась множественным наследованием, в java такого нет. Как быть?

Примерный псевдокод того, что хочу реализовать:

class Base < static protected final String someString = "01"; >class Child1 extends Base < static public char get_0() < return someString.charAt(0); >> class Child2 extends Base < static public char get_1() < return someString.charAt(1); >> class MixIn extends Child1, Child2 < // Так сделать нельзя >public class Main < public static void main(String[] args) < System.out.println(MixIn.get_0()); // Итоговое использование должно быть таким System.out.println(MixIn.get_1()); >> 

Отслеживать
задан 11 дек 2020 в 9:21
Mikhail Murugov Mikhail Murugov
5,436 1 1 золотой знак 16 16 серебряных знаков 36 36 бронзовых знаков

В Java есть понятие interface, вы можете свои Child классы реализовать с их помощью. Множественное наследование интерфейсов допустимо

11 дек 2020 в 9:27

у всех Ваших классов будет один общий предок. И тут возникает целый спектр разных проблем. Поэтому в Java и решили отказаться от этого. Но можно делать множественное «наследование» от интерфейсов. А у интерфейсов теперь есть «имплементация методов»:)

11 дек 2020 в 9:28

Если Base — хранилище, используйте композицию. Использование наследования здесь будет семантически неверно, т.к. наследование классов, выполняющих операции над данными, от класса Base , не представляет отношение IS-A

11 дек 2020 в 9:29

Все было понятно пока не появились статические методы. Вы просто так добавили static? Для статических методов наследование здесь имеет мало смысла. Почему нельзя написать Child1.get_0() и Child2.get_1() если методы не переопределяются все равно? Может доработаете пример чтобы было понятнее что именно Вам нужно?

11 дек 2020 в 9:30

Возможно, паттерн стратегия подойдет. Классы ChildN выполняют только действия, поэтому их вполне можно заменить на стратегии.

11 дек 2020 в 9:32

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

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

Использование интерфейсов

Вы можете сделать child-ы интерфейсами. В Java нет множественно наследования, зато можно имплементировать несколько интерфейсов. Чтобы сделать реализацию методов прямо в интерфейсах (чтобы они не требовали реализации в реализующем их классе), можно использовать ключевое слово default :

class Base < static public final String str = "01"; >interface Child1 < default char get0() < return Base.str.charAt(0); >> interface Child2 < default char get1() < return Base.str.charAt(1); >> class MixIn implements Child1, Child2 < >class Main < public static void main(String[] args) < MixIn mixIn = new MixIn(); System.out.println(mixIn.get0()); System.out.println(mixIn.get1()); >> 

Отказ от разделения функционала

Вы работаете с одним репозиторием Base. На мой взгляд здесь более чем достаточно использование одного класса без всяких интерфейсов. Более того, можно все эти методы разместить в классе Base.

class Base < private final String str = "01"; public String getStr() < return str; >> class MixIn < private Base base; public MixIn(Base base) < this.base = base; >public char get0() < return base.getStr().charAt(0); >public char get1() < return base.getStr().charAt(1); >> public class Main < public static void main(String[] args) < MixIn mixIn = new MixIn(new Base()); System.out.println(mixIn.get0()); System.out.println(mixIn.get1()); >> 

Использование статических внутренних классов

Вы говорите, что вам нужна логическая группировка. В Java существует конструкция, которая называется статический внутренний или вложенный класс. Внутри класса создаётся ещё один класс, который отмечается static . Таким образом вы можете достичь логической группировки. Пример:

class Base < static public final String someString = "01"; >class MixIn < public static class Child1 < static char get1() < return Base.someString.charAt(1); >> public static class Child2 < static char get0() < return Base.someString.charAt(0); >> > 

Видите, я создал общий класс MixIn, а внутри него два статических класса. Вызываются они вот так:

class Main < public static void main(String[] args) < System.out.println(MixIn.Child1.get1()); System.out.println(MixIn.Child2.get0()); >> 

Как видите, никакого инстанцирование нет, а классы/методы логически сгруппированы.

Наследование в Java

В случае наследования в Java для указания родительского класса используется ключевое слово extends, которое следует после имени дочернего, т. е. производного, класса. После extends пишется родительский класс. Другими словами, «дочерний класс расширяет родительский».

В Java нет множественного наследования от классов, но есть множественное наследование интерфейсов.

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

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

public class Main  public static void main(String[] args)  Child child = new Child(10); System.out.println(child.a); System.out.println(child.b); > >
class Parent  int a; Parent(int a)  this.a = 1; > >
class Child extends Parent  int b; Child(int b)  this.b = b; > >

В приведенном выше примере ошибка возникает на уровне конструктора класса Child. Перед тем как выполнится выражение this.b = b, будет неявно вызван конструктор Parent без параметров. Однако такого конструктора в родительском классе нет.

Если мы закомментируем или удалим конструктор Parent,

class Parent  int a; // Parent(int a) // this.a = 1; // > >

то ошибка в дочернем классе исчезнет. Дело в том, что в Java любой класс есть потомок базового класса Object, у которого всегда имеется пустой дефолтный конструктор. В данном случае, двигаясь по дереву наследования к корню, из Child будет вызываться конструктор базового для всех класса.

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

Заметим, что у объектов Child поле this.a существует, даже если ему не было присвоено значение. В данном случае значение будет нулевым.

Обычным вариантом недопущения подобных ошибок является объявление в Parent конструктора без аргументов. Он может быть пустым, или содержать какой-либо код:

import java.util.Random; class Parent  int a; Parent()  Random random = new Random(); this.a = random.nextInt(10); > >

Другой распространенный вариант – когда в дочернем классе явно вызывается конструктор родительского класса с помощью ключевого слова super. Причем вызывать всегда надо первой строчкой тела конструктора.

public class Main  public static void main(String[] args)  Child child = new Child(10, 20); System.out.println(child.a); System.out.println(child.b); > >
class Parent  int a; Parent(int a)  this.a = a; > >
class Child extends Parent  int b; Child(int a, int b)  super(a); this.b = b; > >

Обычные методы наследуются и переопределяются стандартно для объектно-ориентированного программирования. При этом в Java наследовать и переопределять можно только нестатические методы. Таким образом, если в родительском классе объявлен статический метод, то дочерние классы его не наследуют.

В Java объекты дочернего класса можно присваивать переменным родительского. Подобное называется расширением типа. При этом в случае вызова переопределенных методов будут вызываться методы дочернего класса, а не родительского. Однако набор допустимых для вызова методов определяется родительским классом, то есть типом переменной.

Также возможно сужение типа, когда объект, связанный с переменной родительского типа, присваивается переменной дочернего, то есть своего типа. В этих случаях, чтобы избежать ошибок, следует проверять тип объекта. Например, пусть класс B производный от класса A. Тогда:

A var = new B(); if (var instanceof B)  B var1 = (B) var; >

Если в методе дочернего класса требуется использовать возможности родительского, то обращение ко второму происходит через слово super, после которого пишется необходимый метод.

public class ParentMeth  public static void main(String[] args)  ChildMeth ch = new ChildMeth(); ch.PrintStr(); > public void PrintStr()  System.out.println("Parent class"); > > class ChildMeth extends ParentMeth  public void PrintStr()  super.PrintStr(); System.out.println("Child class"); > >
Parent class Child class

X Скрыть Наверх

Программирование на Java. Курс

Почему считают, что в Java нет множественного наследования?

Множественное наследование — это когда в определении класса может быть задано несколько классов-предков. Но в Java может быть указан только один класс-предок.

Наследование — это ТОЛЬКО «папа». «Дедушки», «прадедушки» и т. д. не входят в наследование.

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

NextМудрец (19662) 2 года назад

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

Next, разница в подходе. Препочтительней использовать интерфейсы. Например, в фразе «и швец, и жнец, и на дуде игрец» мы скорее видим некоего человека который способен выполнять _роль_ швеи, жнеца и исполнителя на духовых инструментах. В случае с наследованием это будет связь _является_. Т. е. замужняя Швея с двумя детьми из города которая также является холостым Комбайнёром (известным по всей деревне ходоком) и ещё с неопределившимся со своей сексуальной ориентацией работником сферы развлечений из большого города

Родительский класс наследует Object, а не твой класс. Унаследовать второй класс ты не сможешь. Только интерфейсы.

Ладно, я передумал, всё же отвечу))
Так считают, потому что ты не сможешь в одном классе унаследоваться от более, чем одного. Вот, можешь сам попробовать вставить такое в ide:
public class Main extends Thread, Exception >
На что ты получишь ошибку Class cannot extend multiple classes

Почему множественное наследование не поддерживается в Java

Это просто устранить двусмысленность, поскольку множественное наследование может вызвать двусмысленность вокруг проблемы Diamond в нескольких сценариях. Например, если у вас есть два родительских класса, они могут иметь разные реализации одной и той же функции — или, возможно, две разные функции с тем же именем. Затем вам нужно решить, какой из них будет использоваться вашим подклассом. Есть способы справиться с этим, но разработчики Java считают, что множественное наследование вызывает больше проблем и путаницы, чем решает. Поэтому они избегают множественного наследования с языка Java.

(Пока оценок нет)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *