Почему множественное наследование запрещено в Java или C #?


115

Я знаю, что в Java и C # множественное наследование запрещено. Во многих книгах прямо говорится, что множественное наследование недопустимо. Но это можно реализовать с помощью интерфейсов. Ничего не обсуждается, почему это запрещено. Кто-нибудь может сказать мне, почему это запрещено?


1
Так же , как указать на то , что там есть рамки там, позволяющие МИ-как поведение в классе C #. И, конечно же, у вас есть возможность использовать миксины без состояния (с использованием методов расширений) - без сохранения состояния они не очень полезны.
Дмитрий Нестерук

1
То, что интерфейсы имеют какое-либо отношение к наследованию, - это иллюзия, созданная синтаксисом языка. Наследование должно сделать вас богаче. Ничего подобного для интерфейса, вы не наследуете приседания, и это делает вас значительно беднее. Вы унаследовали игровой долг дяди Гарри, вам нужно еще поработать, чтобы реализовать интерфейс.
Ханс

Ответы:


143

Короткий ответ: потому что разработчики языка решили не делать этого.

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

Для более увлекательного и подробного чтения в сети доступны статьи с интервью с некоторыми разработчиками языков. Например, для .NET Крис Брамм (который работал в MS над CLR) объяснил причины, по которым они решили не делать этого:

  1. На разных языках действительно разные ожидания относительно того, как работает MI. Например, как разрешаются конфликты и объединяются ли повторяющиеся базы или дублируются. Прежде чем мы сможем реализовать MI в CLR, мы должны провести обзор всех языков, выяснить общие концепции и решить, как выразить их нейтральным языком. Нам также нужно будет решить, принадлежит ли MI к CLS и что это будет значить для языков, которым не нужна эта концепция (например, предположительно VB.NET). Конечно, это бизнес, которым мы занимаемся как среда CLR, но у нас еще нет времени делать это для MI.

  2. Количество мест, где действительно подходит ИМ, на самом деле довольно мало. Во многих случаях вместо этого может быть выполнено множественное наследование интерфейсов. В других случаях вы можете использовать инкапсуляцию и делегирование. Если бы мы добавили немного другую конструкцию, например миксины, было бы это действительно более мощным?

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

Вы можете прочитать всю статью здесь.

Для Java вы можете прочитать эту статью :

Причины отказа от множественного наследования в языке Java в основном связаны с «простой, объектно-ориентированной и знакомой» целью. Создатели Java хотели получить простой язык, который мог бы освоить большинство разработчиков без серьезной подготовки. С этой целью они работали над тем, чтобы сделать язык максимально похожим на C ++ (знакомым), не перенося излишнюю сложность C ++ (простой).

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


10
Хорошее сравнение. Я думаю, это весьма показательно для мыслительных процессов, лежащих в основе обеих платформ.
CurtainDog

97

Множественное наследование реализации недопустимо.

Проблема в том, что компилятор / среда выполнения не может понять, что делать, если у вас есть классы Cowboy и Artist, оба с реализациями для метода draw (), а затем вы пытаетесь создать новый тип CowboyArtist. Что происходит, когда вы вызываете метод draw ()? Кто-то лежит мертвый на улице, или у вас есть прекрасная акварель?

Я считаю, что это называется проблемой наследования двойного ромба.


80
Вы получаете прекрасную акварель, на которой изображен мертвец на улице :-)
Дэн Ф

Это единственная проблема? Я думаю, я не уверен, что C ++ решает эту проблему с помощью ключевого слова virtual, это правда? Я не очень хорошо
разбираюсь

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

1
В современном дизайне композиция предпочтительнее наследования во всех случаях, кроме самых простых. Множественное наследование никогда не считалось бы простым случаем. С помощью композиции у вас есть точный контроль над тем, что ваш класс делает, когда возникают проблемы с бриллиантами, подобные этой ...
Билл Мичелл

Вам просто понадобится список приоритетов. Это используется в объектной системе Common Lisp, CLOS. Все классы образуют иерархию, и вызываемый метод определяется оттуда. Кроме того, CLOS позволяет вам определять различные правила, например, CowboyArtist.draw () может сначала нарисовать Ковбоя, а затем Художника. Или что бы вы ни придумывали.
Себастьян Крог,

19

Причина: Java очень популярна, и ее легко кодировать из-за ее простоты.

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

  1. Они избегали указателей
  2. Они избежали множественного наследования.

Проблема с множественным наследованием: проблема с алмазом.

Пример :

  1. Предположим, что у класса A есть метод fun (). класс B и класс C происходит от класса A.
  2. И оба класса B и C переопределяют метод fun ().
  3. Теперь предположим, что класс D наследует как класс B, так и класс C. (только предположение)
  4. Создайте объект для класса D.
  5. D d = новый D ();
  6. и попробуйте получить доступ к d.fun (); => будет ли он называть fun () класса B или fun () класса C?

Это неоднозначность, существующая в алмазной проблеме.

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

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


1
«Но в любом случае вы всегда можете реализовать множественное наследование косвенно, используя интерфейсы». - что требует, чтобы программист каждый раз заново определял тело методов.
Кари

13

Потому что у Java философия дизайна сильно отличается от C ++. (Я не собираюсь здесь обсуждать C #.)

При разработке C ++ Страуструп хотел включить полезные функции, независимо от того, как они могут быть использованы неправильно. Можно сильно облажаться с множественным наследованием, перегрузкой операторов, шаблонами и различными другими функциями, но с ними также можно делать очень хорошие вещи.

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

Кроме того, Java в значительной степени была реакцией C ++ и Smalltalk, наиболее известных объектно-ориентированных языков. Существует множество других объектно-ориентированных языков (Common Lisp был фактически первым, который был стандартизирован), с различными системами объектно-ориентированного программирования, которые лучше справляются с MI.

Не говоря уже о том, что MI на Java вполне возможно, используя интерфейсы, композицию и делегирование. Он более явный, чем в C ++, и, следовательно, более неуклюж в использовании, но даст вам то, что вы с большей вероятностью поймете с первого взгляда.

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


12

Основная (хотя ни в коем случае не единственная) причина, по которой люди избегают МИ, - это так называемая «проблема алмаза», приводящая к двусмысленности в вашей реализации. Эта статья в Википедии обсуждает это и объясняет лучше, чем я. MI также может привести к более сложному коду, и многие разработчики объектно-ориентированных приложений утверждают, что вам не нужен MI, и если вы его используете, ваша модель, вероятно, неверна. Я не уверен, что согласен с этим последним пунктом, но всегда сохранять простоту - хороший план.


8

В C ++ множественное наследование было серьезной головной болью при неправильном использовании. Чтобы избежать этих популярных проблем с дизайном, в современных языках (java, C #) вместо этого было принудительно реализовано «наследование» нескольких интерфейсов.


8

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

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

Таким образом, можно считать разумным не включать множественное наследование в язык Java.


3
Я не согласен со всем вышесказанным, поскольку работал с объектной системой Common Lisp.
Дэвид Торнли

2
Динамические языки не в счет ;-) В любой Lisp-подобной системе у вас есть REPL, который упрощает отладку. Кроме того, CLOS (насколько я понимаю, я никогда не использовал ее, только читал о ней) - это мета-объектная система с большой гибкостью и индивидуальным подходом. Но рассмотрим статически скомпилированный язык, такой как C ++, где компилятор генерирует дьявольски сложный поиск метода с использованием нескольких (возможно, перекрывающихся) vtables: в такой реализации выяснение того, какая реализация метода была вызвана, может быть нетривиальной задачей.
mfx

5

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


4

В старые времена (70-е годы), когда информатика была больше наукой и менее массовым производством, у программистов было время подумать о хорошем дизайне и хорошей реализации, и в результате продукты (программы) имели высокое качество (например, дизайн TCP / IP и реализация). В настоящее время, когда все программируют, а менеджеры меняют спецификации до истечения крайних сроков, трудно отследить тонкие проблемы, подобные описанной в ссылке на википедию из сообщения Стива Хая; поэтому «множественное наследование» ограничено конструкцией компилятора. Если вам это нравится, вы все равно можете использовать C ++ .... и иметь всю свободу, которую хотите :)


4
... включая возможность выстрелить себе в ногу несколько раз;)
Марио Ортегон

4

Я отношусь к утверждению, что «Множественное наследование запрещено в Java», с долей скепсиса.

Множественное наследование определяется, когда «Тип» наследуется от более чем одного «Типа». И интерфейсы также классифицируются как типы по своему поведению. Итак, у Java есть множественное наследование. Просто так безопаснее.


6
Но вы не наследуете интерфейс, вы его реализуете. Интерфейсы - это не классы.
Blorgbeard отсутствует

2
Да, но наследование никогда не определялось так узко, за исключением некоторых предварительных книг. И я думаю, что нам следует выйти за рамки грамматики. Оба они делают одно и то же, и интерфейсы «изобрели» только для этого.
Ригведа,

Возьмем интерфейс Closeable: один метод close (). Предположим, вы хотите расширить это до Openable: у него есть метод open (), который устанавливает для логического поля isOpen значение true. Попытка открыть Openable, который уже открыт, вызывает исключение, как и попытка закрыть Openable, который не открыт ... Сотни более интересных генеалогий классов могут захотеть принять такую ​​функциональность ... без необходимости расширять каждый время из класса Openable. Если бы Openable был просто интерфейсом, он не смог бы обеспечить эту функциональность. QED: интерфейсы не обеспечивают наследование!
Майк грызун

4

Динамическая загрузка классов затрудняет реализацию множественного наследования.

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

алмазная проблема множественного наследования. У нас есть два класса B и C, наследующие от A. Предположим, что B и C переопределяют унаследованный метод и предоставляют свою собственную реализацию. Теперь D наследуется как от B, так и от C, выполняя множественное наследование. D должен унаследовать этот переопределенный метод, jvm не может решить, какой переопределенный метод будет использоваться?

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

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


3

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


2

В Java есть понятие, то есть полиморфизм. В java есть 2 типа полиморфизма. Есть перегрузка метода и переопределение метода. Среди них переопределение метода происходит с отношениями суперкласса и подкласса. Если мы создаем объект подкласса и вызываем метод суперкласса, и если подкласс расширяет более одного класса, какой метод суперкласса следует вызывать?

Или при вызове конструктора суперкласса super(), какой конструктор суперкласса будет вызван?

Это решение невозможно из-за текущих возможностей Java API. поэтому множественное наследование в java запрещено.


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

1

Множественное наследование не разрешено в Java напрямую, но разрешено через интерфейсы.

Причина:

Множественное наследование: вносит большую сложность и неоднозначность.

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

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

Возьмем простой пример.

  1. Предположим, что существует 2 суперкласса класса A и B с одинаковыми именами методов, но с разными функциями. В следующем коде с ключевым словом (extends) множественное наследование невозможно.

       public class A                               
         {
           void display()
             {
               System.out.println("Hello 'A' ");
             }
         }
    
       public class B                               
          {
            void display()
              {
                System.out.println("Hello 'B' ");
              }
          }
    
      public class C extends A, B    // which is not possible in java
        {
          public static void main(String args[])
            {
              C object = new C();
              object.display();  // Here there is confusion,which display() to call, method from A class or B class
            }
        }
  2. Но через интерфейсы с ключевым словом (реализация) множественное наследование возможно.

    interface A
        {
           // display()
        }
    
    
     interface B
        {
          //display()
        }
    
     class C implements A,B
        {
           //main()
           C object = new C();
           (A)object.display();     // call A's display
    
           (B)object.display(); //call B's display
        }
    }

0

Кто-нибудь может сказать мне, почему это запрещено?

Вы можете найти ответ по этой ссылке документации

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

Если разрешено множественное наследование и когда вы создаете объект, создавая экземпляр этого класса, этот объект будет наследовать поля от всех суперклассов класса. Это вызовет две проблемы.

  1. Что, если методы или конструкторы из разных суперклассов создают экземпляр одного и того же поля?

  2. Какой метод или конструктор будет иметь приоритет?

Несмотря на то, что теперь разрешено множественное наследование состояния, вы все же можете реализовать

Множественное наследование типа : способность класса реализовывать более одного интерфейса.

Множественное наследование реализации (через методы по умолчанию в интерфейсах): возможность наследовать определения методов из нескольких классов

Обратитесь к соответствующему вопросу SE для получения дополнительной информации:

Неоднозначность множественного наследования с интерфейсом


0

В C ++ класс может наследовать (прямо или косвенно) более чем от одного класса, что называется множественным наследованием. .

Однако C # и Java ограничивают классы одним наследованием, которое каждый класс наследует от одного родительского класса.

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

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

Проблема множественного наследования в том, что оно может привести к неоднозначности. Классический пример - это когда класс наследуется от двух других классов, каждый из которых наследуется от того же класса:

class A {
    protected:
    bool flag;
};
class B : public A {};
class C : public A {};
class D : public B, public C {
    public:
    void setFlag( bool nflag ){
        flag = nflag; // ambiguous
    }
};

В этом примере flagчлен данных определяется как class A. Но class Dнисходит class B и class C, которые оба происходят из A, так что по сути две копии из flagдоступны , потому что два экземпляра Aв Dиерархии классов «S. Какой из них вы хотите установить? Компилятор будет жаловаться , что ссылка на flagин Dявляется неоднозначной . Одно исправление - явное устранение неоднозначности ссылки:

B::flag = nflag;

Еще одно исправление - объявить B и C как virtual base classes , что означает, что в иерархии может существовать только одна копия A, что устраняет любую двусмысленность.

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

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


-1

Представьте себе этот пример: у меня есть класс Shape1

У него есть CalcualteAreaметод:

Class Shape1
{

 public void CalculateArea()

     {
       //
     }
}

Есть еще один класс Shape2, у которого также есть тот же метод

Class Shape2
{

 public void CalculateArea()

     {

     }
}

Теперь у меня есть дочерний класс Circle, он является производным от Shape1 и Shape2;

public class Circle: Shape1, Shape2
{
}

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

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


конструктор языка может дать явный вызов метода, как в интерфейсе. Нет такой точки! Другая причина в том, как насчет абстрактного класса с абстрактными методами теперь, как вы это считаете?
Номи Али
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.