Почему алмазный случай с его общим предком используется для объяснения проблемы множественного наследования Java вместо двух несвязанных родительских классов?

этот вопрос может показаться странным для людей Java, но если вы попытаетесь объяснить это, это было бы здорово.

в эти дни я очищаю некоторые из самых основных концепций Java. Поэтому я перехожу к теме наследования и интерфейса Java.

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

скажем, у меня есть класс A и класс B, эти два класса разные (они не являются дочерним классом общего класса), но у них есть один общий метод, и они выглядят так: -

class A {
    void add(int a, int b) {

    }
}

class B {
    void add(int a, int b) {

    }
}

хорошо, теперь скажем, если Java поддерживает множественное наследование и если есть один класс, который является подклассом A и B, как это :-

class C extends A,B{ //If this was possible
    @Override
    void add(int a, int b) { 
        // TODO Auto-generated method stub
        super.add(a, b); //Which version of this, from A or B ?
    }
 }

тогда компилятор не сможет найти, какой метод для вызова из или B, и именно поэтому Java не поддерживает множественное наследование. Так есть ли что-то неправильное в этой концепции ?

когда я читал об этой теме, я смог понять проблему с алмазами, но я не могу понять, почему люди не дают пример с тремя классами (если это действительно один, потому что мы использовали только 3 класса, чтобы продемонстрировать проблему, поэтому его легко понять, сравнивая его с алмазной проблемой.)

Дайте мне знать, не подходит ли этот пример для объяснения проблемы или это также можно отнести к пониманию проблемы.

Edit: Я получил один близкий голос здесь, заявив, что этот вопрос не ясен. Вот главный вопрос :-

могу ли я понять, почему "Java не поддерживает множественное наследование" с 3 классами только, как описано выше, или мне нужно иметь 4 класса (Алмазная структура), чтобы понять проблему.

6 ответов


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

только с тремя классами проблема решается относительно легко, вводя простую конструкцию, такую как super.A или super.B. И хотя вы смотрите только на переопределенные методы, это действительно не так неважно, есть ли у вас общий предок или только три основных класса.

если A и B имеют общего предка, состояние которого они оба наследуют, то у вас серьезные неприятности. Вы храните две отдельные копии состояния этого общего предка? Это было бы больше похоже на композицию, чем на наследование. Или вы храните только один, который разделяется обоими A и B, вызывая странные взаимодействия, когда они манипулируют своим унаследованным общим государство?
class A {
  protected int foo;
}

class B extends A {
  public B() {
    this.foo = 42;
  }
}

class C extends A {
  public C() {
    this.foo = 0xf00;
  }
}

class D extends B,C {
  public D() {
    System.out.println( "Foo is: "+foo ); //Now what?
  }
}

обратите внимание, что не было бы такой большой проблемой, если класс A не существует, а как B и C объявили собственную


Java не поддерживает множественное наследование, потому что разработчики языка разработали Java таким образом. Другие языки, такие как C++ поддерживает множественное наследование, но это не техническая проблема, а просто критерии проектирования.

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

C++ решает проблему класса в форме алмаза с виртуальное наследование:

виртуальное наследование-это метод, используемый в объектно-ориентированном Программирование, где определенный базовый класс в иерархии наследования объявляется, чтобы поделиться своими экземплярами данных-членов с любым другим включения той же базы в дальнейшие производные классы. Например, если класс A обычно (не виртуально) производный от класса X (предполагается чтобы содержать члены данных), а также Класс B, А Класс C наследует из обоих классов A и B, он будет содержать два набора данных члены, связанные с классом X (доступны независимо, часто с подходит простая и ясная отбор). Но если класс A практически производный от класса X, вместо этого объекты класса C будут содержать только один набор членов данных из класса X. Самый известный язык что реализует эту функцию C++.

В отличие от Java, в C++ вы можете устранить двусмысленность, какой метод экземпляра вызывать, префиксом вызова с именем класса:

class X {
  public: virtual void f() { 

  } 
};

class Y : public X {
  public: virtual void f() { 

  } 
};

class Z : public Y {
  public: virtual void f() { 
    X::f();
  } 
};

Это всего лишь одна трудность, которую вам нужно решить для множественного наследования на языке. Поскольку существуют языки, которые имеют множественную наследственность (например, Common Lisp, C++, Eiffel), это, очевидно, не непреодолимо.

Common Lisp определяет точную стратегию приоритизации (упорядочения) графа наследования, поэтому в редких случаях, когда это имеет значение на практике, нет двусмысленности.

C++ использует виртуальное наследование (я еще не приложил усилий, чтобы понимаю, что это значит).

Eiffel позволяет точно указать, как вы хотите наследовать, возможно, переименование методов в подклассе.

Java просто пропустил множественное наследование. (Я лично считаю, что трудно не оскорблять своих дизайнеров, пытаясь рационализировать это решение. Конечно, трудно думать о чем, если язык вам кажется!--9-->на не поддерживает его.)


проблема алмаза с четырьмя классами проще, чем проблема трех классов, описанная в вопросе.

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

путем добавления четвертого класса, которая определяет add понятно, что как A и B осуществляют то же самое add метод.

таким образом, проблема diamond более ясна, потому что она показывает проблему с использованием простого наследования, а не просто с использованием методов с той же сигнатурой. Хорошо известно, и, вероятно, применяется к более широкому диапазону языков программирования, поэтому изменение его с тремя классами (что добавляет дополнительную проблему конфликта имен) не имеет большого смысла и поэтому не было принято.


множественное наследование не является проблемой, если вы найдете разумное решение для разрешения метода. Когда вы вызываете метод на объекте, разрешение метода-это акт выбора, какая версия класса метода должна использоваться. Для этого график наследования линеаризуется. Затем выбирается первый класс в этой последовательности, предоставляющий реализацию запрошенного метода.

чтобы проиллюстрировать следующие примеры стратегий разрешения конфликтов, мы будем использовать это график наследования алмазов:

    +-----+
    |  A  |
    |=====|
    |foo()|
    +-----+
       ^
       |
   +---+---+
   |       |
+-----+ +-----+
|  B  | |  C  |
|=====| |=====|
|foo()| |foo()|
+-----+ +-----+
   ^       ^
   |       |
   +---+---+
       |
    +-----+
    |  D  |
    |=====|
    +-----+
  • самая гибкая стратегия требует от программиста явно указать реализация при создании неоднозначного класса путем явного переопределения конфликтующего метода. Одним из вариантов этого является запрет множественного наследования. Если программист хочет наследовать поведение от нескольких классов, необходимо использовать композицию и написать несколько прокси-методов. Однако, наивно явно разрешенные конфликты наследования имеют те же недостатки, что и...

  • глубина первый поиск, что может создать линеаризацию D, B, A, C. Но сюда,A::foo() считается, что до C::foo() хотя C::foo() переопределяет A::foo()! Это не может быть тем, чего мы хотели. Примером языка, использующего DFS, является Perl.

  • использовать умный алгоритм это гарантирует, что если X подкласс Y, это всегда будет раньше Y в линеаризации. Такой алгоритм не сможет распутать все графы наследования, но в большинстве случаев он обеспечивает здравую семантику: если класс переопределяет метод, он всегда будет предпочтительнее переопределенного метода. Этот алгоритм существует и называется C3. Это создаст линеаризацию D, B, C, A. C3 был впервые представлен в 1996 году. К сожалению, Java была опубликована в 1995 году-поэтому C3 не было известно, когда Java был первоначально разработан.

  • используйте композицию, а не наследование – revisited. некоторые решения множественного наследования предлагают избавиться от бита "наследование классов" и вместо этого предлагают другие единицы композиции. Один из примеров -mixins, которые" копировать и вставлять " определения методов в ваш класс. Это невероятно грубо.

    идея mixins была уточнена в черт (представлено в 2002 году, также слишком поздно для Java). Черты являются более общим случаем как классов, так и интерфейсов. Когда вы" наследуете " признак, определения внедряются в ваш класс, чтобы это не усложняло разрешение метода. В отличие от mixins, черты обеспечивают более тонкие стратегии для разрешения конфликтов. Особенно важен порядок, в котором складываются черты. Черты играют заметную роль в объектной системе "лося" Perl (называемой роли) и в Scala.

Java предпочитает одиночное наследование по другой причине: если каждый класс может иметь только один суперкласс, нам не нужно применять сложные алгоритмы разрешения метода – цепочка наследования уже является порядком разрешения метода. Это делает способ более эффективным.

Java 8 представил методы по умолчанию, которые выглядят похожими на черты. Однако правила разрешения метода Java делают интерфейсы с методами по умолчанию гораздо менее способный, чем черты характера. Тем не менее, шаг в направлении адаптации современных решений проблемы множественного наследования.

в большинстве схем разрешения метода множественного наследования порядок суперклассов имеет значение. То есть, есть разница между class D extends B, C и class D extends C, B. Поскольку порядок можно использовать для простого устранения неоднозначности, пример трех классов недостаточно демонстрирует проблемы, связанные с множественным наследованием. Вам нужен полный четырехклассный Алмазная проблема для этого, поскольку она показывает, как наивный поиск глубины приводит к неинтуитивному порядку разрешения метода.


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

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