Подтвердить что ты не робот

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

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

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

Прочитав это, я обнаружил, что Java не поддерживает множественное наследование и также понимает, что я не могу понять, почему всюду проблема с алмазной фигурой (как минимум 4 класса для создания алмаза) обсуждается, чтобы объяснить это поведение, Can Мы не понимаем эту проблему, используя только 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 ?
    }
 }

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

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

Сообщите мне, не подходит ли этот пример для объяснения проблемы, или это также может быть передано для понимания проблемы.

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

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

4b9b3361

Ответ 1

Проблема с наследованием алмазов - это не столько совместное поведение, но общее состояние. Как вы можете видеть, 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 объявили свое собственное поле foo. По-прежнему будет проблема с именами конфликтов, но это может быть разрешено с помощью некоторой конструкции пространства имен (B.this.foo и C.this.foo, возможно, как и для внутренних классов?). Истинная проблема с алмазами, с другой стороны, больше, чем столкновение с именами, вопрос о том, как поддерживать инварианты классов, когда два несвязанных суперкласса D (B и C) разделяют одно и то же состояние, которое они оба наследуют от A. Вот почему все четыре класса необходимы, чтобы продемонстрировать всю полноту проблемы.

Совместное поведение при множественном наследовании не проявляет той же проблемы. Настолько, что недавно введенные методы по умолчанию делают именно это. Это означает, что теперь допускается множественное наследование реализаций. По-прежнему существует некоторая сложность в решении, для реализации которой нужно позвонить, но поскольку интерфейсы не имеют апатридов, самый большой bugbear исключается.

Ответ 2

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

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

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

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

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

class X {
  public: virtual void f() { 

  } 
};

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

  } 
};

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

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

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

Ответ 5

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

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

    +-----+
    |  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 была изначально разработана.

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

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

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

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

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

Ответ 6

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

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