Почему в Java нет множественного наследования, но разрешена реализация нескольких интерфейсов?


153

Java не допускает множественное наследование, но позволяет реализовать несколько интерфейсов. Зачем?


1
Я отредактировал название вопроса, чтобы сделать его более наглядным.
Божо

4
Интересно, что в JDK 8 будут методы расширения, которые позволят определить реализацию методов интерфейса. Правила были определены, чтобы управлять множественным наследованием поведения, но не состояния (что, как я понимаю, более проблематично .
Эдвин Далорсо,

1
Прежде чем вы, ребята, тратите время на ответы, которые говорят только о том, «как java достигает множественного наследования» ... Я предлагаю вам перейти к ответу @Prabal Srivastava, который логически дает вам то, что должно происходить внутри, чтобы не позволить классам это право .. и только разрешить интерфейсам право разрешать множественное наследование.
Yo Apps

Ответы:


228

Поскольку интерфейсы определяют только то, что делает класс, а не как он это делает.

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


8
Я делал C ++ и сталкивался с той же проблемой довольно много раз. Недавно я читал о том, что у Scala есть «черты», которые кажутся мне чем-то средним между «C ++» и «Java».
Нильс Басьес

2
Таким образом, вы избегаете «проблемы с алмазами»: en.wikipedia.org/wiki/Diamond_problem#The_diamond_problem
Ник Л.

6
Начиная с Java 8 вы можете определить два идентичных метода по умолчанию, по одному в каждом интерфейсе. Если вы реализуете оба интерфейса в своем классе, вы должны переопределить этот метод в самом классе, см .: docs.oracle.com/javase/tutorial/java/IandI/…
bobbel

6
Этот ответ не точен. Проблема не в том, чтобы указать, как и Java 8 там доказать. В Java 8 два суперинтерфейса могут объявлять один и тот же метод в разных реализациях, но это не проблема, поскольку методы интерфейса являются виртуальными , поэтому вы можете просто перезаписать их, и проблема будет решена. Реальная проблема заключается в неоднозначности в атрибутах , потому что вы не можете решить эту неоднозначность, перезаписывая атрибут (атрибуты не являются виртуальными).
Алекс Оливейра

1
Что вы подразумеваете под "атрибутами"?
Божо

96

Один из преподавателей моего колледжа объяснил мне это так:

Предположим, у меня есть один класс, который является Toaster, и другой класс, который является NuclearBomb. Они оба могут иметь настройку «тьмы». У них обоих есть метод on (). (У одного есть off (), у другого нет.) Если я хочу создать класс, который является подклассом обоих из них ... как вы можете видеть, это проблема, которая может действительно взорваться мне в лицо ,

Таким образом, одна из основных проблем заключается в том, что если у вас есть два родительских класса, они могут иметь разные реализации одной и той же функции или, возможно, две разные функции с одинаковым именем, как в примере моего инструктора. Затем вы должны решить, какой из ваших подклассов использовать. Конечно, есть способы справиться с этим - C ++ делает это - но разработчики Java чувствовали, что это усложнит ситуацию.

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


5
Спасибо, Номен. Источником цитаты был аспирант по имени Брендан Бернс, который в то время также работал с репозиторием открытого исходного кода Quake 2. Пойди разберись.
Синтаксический

4
Проблема с этой аналогией состоит в том, что если вы создаете подкласс ядерной бомбы и тостера, то «ядерный тостер» может разумно взорваться при использовании.
101100

20
Если кто-то решит смешать ядерную бомбу и тостер, он заслуживает того, чтобы бомба взорвалась ему в лицо. Цитата ошибочных рассуждений
Масуд

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

24

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

public class MyGodClass extends AppDomainObject, HttpServlet, MouseAdapter, 
             AbstractTableModel, AbstractListModel, AbstractList, AbstractMap, ...

Можете ли вы объяснить, почему вы говорите, что наследование чрезмерно используется? Создание класса бога - это именно то, что я хочу сделать! Я нахожу так много людей, которые работают над одиночным наследованием, создавая классы «Инструменты» со статическими методами.
Дункан Калверт

9
@DuncanCalvert: Нет, вы не хотите этого делать, если только этот код не будет нуждаться в обслуживании. Многие статические методы упускают смысл ОО, но чрезмерное множественное наследование намного хуже, потому что вы полностью теряете представление о том, где и где используется код, а также о том, что концептуально является классом. Оба пытаются решить проблему «как я могу использовать этот код там, где он мне нужен», но это простая краткосрочная проблема. Гораздо сложнее долгосрочная проблема, решаемая с помощью правильного ОО-дизайна: «Как мне изменить этот код без непредвиденного прерывания программы в 20 разных местах?»
Майкл Боргвардт,

2
@DuncanCalvert: и вы решаете это, имея классы с высокой связностью и низкой связью, что означает, что они содержат фрагменты данных и код, которые интенсивно взаимодействуют друг с другом, но взаимодействуют с остальной частью программы только через небольшой простой публичный API. Тогда вы можете думать о них с точки зрения этого API, а не о внутренних деталях, что важно, потому что люди могут одновременно помнить только ограниченное количество деталей.
Майкл Боргвардт,

18

Ответ на этот вопрос заключается во внутренней работе компилятора Java (цепочки конструктора). Если мы видим внутреннюю работу компилятора Java:

public class Bank {
  public void printBankBalance(){
    System.out.println("10k");
  }
}
class SBI extends Bank{
 public void printBankBalance(){
    System.out.println("20k");
  }
}

После компиляции это выглядит так:

public class Bank {
  public Bank(){
   super();
  }
  public void printBankBalance(){
    System.out.println("10k");
  }
}
class SBI extends Bank {
 SBI(){
   super();
 }
 public void printBankBalance(){
    System.out.println("20k");
  }
}

когда мы расширяем класс и создаем его объект, одна цепочка конструктора будет работать до Object класса.

Выше код будет работать нормально. но если у нас есть другой класс с именем Carрасширяемый Bankи один гибридный (множественное наследование) класс с именем SBICar:

class Car extends Bank {
  Car() {
    super();
  }
  public void run(){
    System.out.println("99Km/h");
  }
}
class SBICar extends Bank, Car {
  SBICar() {
    super(); //NOTE: compile time ambiguity.
  }
  public void run() {
    System.out.println("99Km/h");
  }
  public void printBankBalance(){
    System.out.println("20k");
  }
}

В этом случае (SBICar) не удастся создать цепочку конструктора ( неоднозначность времени компиляции ).

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

За новой концепцией defaultи staticметодом обращайтесь по умолчанию в интерфейс .

Надеюсь, что это решит ваш запрос. Спасибо.


7

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


В чем проблема с «алмазом смерти»?
любопытный парень

2
@curiousguy Содержит более одного подобъекта одного и того же базового класса, неоднозначность (переопределение, из которого используется базовый класс), сложные правила разрешения такой неоднозначности.
Тадеуш Копец

@curiousguy: Если инфраструктура предусматривает, что приведение ссылки на объект к ссылке базового типа будет сохранять идентичность, тогда каждый экземпляр объекта должен иметь ровно одну реализацию любого метода базового класса. Если ToyotaCarи HybridCarоба получены Carи переопределеныCar.Drive , и если PriusCarунаследованы оба, но не переопределеныDrive , система не сможет определить, что Car.Driveдолжен делать виртуал. Интерфейсы избегают этой проблемы, избегая выделенного курсивом условия выше.
суперкат

1
@supercat "система не сможет определить, что должен делать виртуальный Car.Drive". <- Или это может просто привести к ошибке компиляции и заставить вас явно ее выбрать, как это делает C ++.
Крис Миддлтон

1
@ChrisMiddleton: метод void UseCar(Car &foo); Нельзя ожидать, что они будут содержать неоднозначность между ToyotaCar::Driveи HybridCar::Drive(поскольку часто не нужно ни знать, ни заботиться о том, чтобы эти другие типы вообще существовали ). Язык может, как это делает C ++, требовать, чтобы код с ToyotaCar &myCarжеланием передать его UseCarдолжен сначала привести к либо, HybridCarлибо ToyotaCar, но, поскольку ((Car) (HybridCar) myCar) .Drive` и ((Car)(ToyotaCar)myCar).Drive будет делать разные вещи, это будет означать, что upcasts не были сохранением идентичности.
суперкат

6

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

  1. Множественное наследование состояний: возможность наследовать поля от нескольких классов

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

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

    1. Что если методы или конструкторы из разных суперклассов создают одно и то же поле?
    2. Какой метод или конструктор будет иметь приоритет?
  2. Множественное наследование реализации: Возможность наследовать определения методов от нескольких классов.

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

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

    Для получения более подробной информации о решении проблемы алмазов см. Ниже.

    Каковы различия между абстрактными классами и интерфейсами в Java 8?

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

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



4

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

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

В официальном документе Джеймса Гослинга под названием «Java: обзор», опубликованном в феврале 1995 года ( ссылка ), дается представление о том, почему множественное наследование не поддерживается в Java.

По словам Гослинга:

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


ссылка не доступна. Пожалуйста, проверьте и обновите его.
MashukKhan

3

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

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

Интерфейс - это контракт вещей, которые должен реализовать ваш класс. Вы не получаете никакой функциональности от интерфейса. Наследование позволяет вам наследовать функциональность родительского класса (и в случае множественного наследования это может привести к путанице).

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


10
C # не имеет множественного наследования именно потому, что Java не позволяет этого. Он был разработан гораздо позже, чем Java. Я думаю, что главной проблемой множественного наследования было то, как людей учили использовать его влево и вправо. Идея о том, что делегирование в большинстве случаев является гораздо лучшей альтернативой, просто отсутствовала в начале и середине 90-х годов. Поэтому я помню примеры из учебников, когда Автомобиль - это Колесо, Дверь и Лобовое стекло, а Автомобиль - Колеса, Двери и Лобовое стекло. Таким образом, единственное наследование в Java было реакцией коленного рефлекса на эту реальность.
Александр Погребняк

2
@AlexanderPogrebnyak: выберите два из следующих трех: (1) разрешить сохранение идентификаторов из ссылки на подтип к ссылке на супертип; (2) Разрешить классу добавлять виртуальные открытые члены без перекомпиляции производных классов; (3) Разрешить классу неявно наследовать виртуальные члены от нескольких базовых классов, не указывая их явно. Я не верю, что любой язык может управлять всеми тремя из вышеперечисленных. Ява выбрала # 1 и # 2, а C # последовал их примеру. Я считаю, что C ++ принимает только № 3. Лично я думаю, что № 1 и № 2 более полезны, чем № 3, но другие могут отличаться.
суперкат

@supercat «Я не верю, что любой язык может управлять всеми тремя из вышеперечисленных» - если смещения элементов данных определяются во время выполнения, а не во время компиляции (как они есть в «Objective-C», не хрупком ABI ") и / или для каждого класса (т. е. каждый конкретный класс имеет свою собственную таблицу смещений членов), тогда я думаю, что все 3 цели могут быть достигнуты.
Парамагнитный круассан

@TheParamagnCroissant: главная семантическая проблема # 1. Если D1и D2как наследовать от B, и каждый переопределяет функцию f, и если objэто экземпляр типа , Sкоторый наследует от обоих D1и , D2но не отменяет f, а затем отливки ссылку на , Sчтобы D1должно дать что - то , чье fиспользует D1Override и отливку , чтобы Bне должен изменить это. Аналогично, приведение ссылки Sна D2должно привести к тому, что fиспользует D2переопределение, а приведение к Bне должно это изменить. Если языку не нужно разрешать добавление виртуальных членов ...
supercat

1
@TheParamagnCroissant: Возможно, и мой список вариантов был несколько упрощен, чтобы уместиться в комментарии, но откладывание до времени выполнения лишает автора D1, D2 или S возможности узнать, какие изменения они могут внести, не нарушая потребители своего класса.
суперкат

2

Поскольку эта тема не близка, я опубликую этот ответ, я надеюсь, что это поможет кому-то понять, почему java не допускает множественное наследование.

Рассмотрим следующий класс:

public class Abc{

    public void doSomething(){

    }

}

В этом случае класс Abc не расширяет ничего, верно? Не так быстро, этот класс неявно расширяет класс Object, базовый класс, который позволяет всем работать в Java. Все является объектом.

Если вы пытаетесь использовать класс выше , вы увидите , что ваш IDE позволяет использовать такие методы , как: equals(Object o), toString()и т.д., но вы не объявите эти методы, они пришли из базового классаObject

Вы можете попробовать:

public class Abc extends String{

    public void doSomething(){

    }

}

Это нормально, потому что ваш класс не будет неявно расширяться, Objectно будет расширяться, Stringпотому что вы это сказали. Рассмотрим следующее изменение:

public class Abc{

    public void doSomething(){

    }

    @Override
    public String toString(){
        return "hello";
    }

}

Теперь ваш класс всегда будет возвращать «привет», если вы вызываете toString ().

Теперь представьте следующий класс:

public class Flyer{

    public void makeFly(){

    }

}

public class Bird extends Abc, Flyer{

    public void doAnotherThing(){

    }

}

Опять же, класс Flyerнеявного extends Object, у которого есть метод toString(), любой класс будет иметь этот метод, поскольку все они расширяются Objectкосвенно, поэтому, если вы вызываете toString()из Bird, какую toString()java придется использовать? От Abcили Flyer? Это произойдет с любым классом, который пытается расширить два или более классов, чтобы избежать такого «коллизии методов», в котором они построили идею интерфейса , в основном вы можете считать их абстрактным классом, который не расширяет Object косвенно . Поскольку они являются абстрактными, они должны быть реализованы классом, который является объектом (вы не можете создать интерфейс самостоятельно, он должен быть реализован классом), поэтому все будет работать нормально.

Чтобы отличать классы от интерфейсов, ключевое слово Implements было зарезервировано только для интерфейсов.

Вы можете реализовать любой интерфейс, который вам нравится, в одном и том же классе, поскольку по умолчанию он ничего не расширяет (но вы можете создать интерфейс, расширяющий другой интерфейс, но, опять же, интерфейс «папа» не будет расширять объект »), поэтому интерфейс просто интерфейс, и они не пострадают от « коллизий сигнатур методов », если они это сделают, компилятор выдаст вам предупреждение, и вам просто нужно изменить сигнатуру метода, чтобы исправить ее (сигнатура = имя метода + параметры + тип возврата) ,

public interface Flyer{

    public void makeFly(); // <- method without implementation

}

public class Bird extends Abc implements Flyer{

    public void doAnotherThing(){

    }

    @Override
    public void makeFly(){ // <- implementation of Flyer interface

    }

    // Flyer does not have toString() method or any method from class Object, 
    // no method signature collision will happen here

}

1

Потому что интерфейс - это просто контракт. А класс на самом деле является контейнером для данных.


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

1

Например, два класса A, B, имеющие один и тот же метод m1 (). И класс C расширяет как A, B.

 class C extends A, B // for explaining purpose.

Теперь класс C будет искать определение m1. Во-первых, он будет искать в классе, если он не нашел, тогда он будет проверять в классе родителей. Оба A, B имеют определение. Итак, здесь возникает неопределенность, какое определение следует выбрать. Так что JAVA НЕ ПОДДЕРЖИВАЕТ МНОГОКРАТНОЕ НАСЛЕДОВАНИЕ.


как насчет того, чтобы компилятор java давал компилятору одинаковые методы или переменные, определенные в обоих родительских классах, чтобы мы могли изменить код ...
siluveru kiran kumar

1

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

  1. В Java каждый класс является потомком Objectкласса. Когда он наследует от более чем одного суперкласса, подкласс получает неоднозначность для приобретения свойства класса Object.
  2. В Java каждый класс имеет конструктор, если мы пишем его явно или не пишем вообще. Первый оператор вызывает super()для вызова конструктора класса ужин. Если в классе более одного суперкласса, он запутывается.

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


0

Возьмем, к примеру, случай, когда у класса A есть метод getSomething, а у класса B - метод getSomething, а класс C расширяет A и B. Что бы произошло, если бы кто-то вызвал C.getSomething? Нет способа определить, какой метод вызывать.

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


2
« Что бы произошло, если бы кто-то назвал C.getSomething. » Это ошибка в C ++. Задача решена.
любопытный парень

В этом суть ... это был контрпример, который, как мне показалось, был ясен. Я указывал, что нет способа определить, какой метод get должен вызываться. Также как примечание, вопрос был связан с java не c ++
Джон Кейн

Извините, я не понимаю, в чем ваша точка зрения. Очевидно, что в некоторых случаях с ИМ возникает двусмысленность. Как это контрпример? Кто утверждал, что МИ никогда не приводит к двусмысленности? « Также, как примечание, вопрос был связан с java, а не c ++ ». Так?
любопытный парень

Я просто пытался показать, почему эта двусмысленность существует и почему она не связана с интерфейсами.
Джон Кейн

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

0

Рассмотрим сценарий, в котором Test1, Test2 и Test3 представляют собой три класса. Класс Test3 наследует классы Test2 и Test1. Если классы Test1 и Test2 имеют один и тот же метод, и вы вызываете его из дочернего объекта класса, будет неоднозначность для вызова метода класса Test1 или Test2, но нет такой неоднозначности для интерфейса, поскольку в интерфейсе нет реализации.


0

Java не поддерживает множественное наследование, многолучевое и гибридное наследование из-за проблемы неоднозначности:

 Scenario for multiple inheritance: Let us take class A , class B , class C. class A has alphabet(); method , class B has also alphabet(); method. Now class C extends A, B and we are creating object to the subclass i.e., class C , so  C ob = new C(); Then if you want call those methods ob.alphabet(); which class method takes ? is class A method or class B method ?  So in the JVM level ambiguity problem occurred. Thus Java does not support multiple inheritance.

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

Ссылочная ссылка: https://plus.google.com/u/0/communities/102217496457095083679


0

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


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

-1

* Это простой ответ, так как я новичок в Java *

Считайте, что есть три класса X, Yи Z.

Таким образом, мы наследуем как X extends Y, Z And, так Yи имеем Zметод alphabet()с тем же типом возврата и аргументами. Этот метод alphabet()в Yговорит отобразить первый алфавит и метод алфавит Zговорит отображение последнего алфавит . Так что тут возникает двусмысленность, когда alphabet()вызывается X. Говорит ли он отображать первый или последний алфавит ??? Так что Java не поддерживает множественное наследование. В случае интерфейсов, рассмотрим Yи Zкак интерфейсы. Таким образом, оба будут содержать объявление методаalphabet() но не определение. Он не скажет, отображать ли первый или последний алфавит или что-то еще, но просто объявит методalphabet(), Так что нет причин поднимать эту двусмысленность. Мы можем определить метод с чем угодно внутри класса X.

Таким образом, одним словом, в Интерфейсах определение делается после реализации, поэтому нет путаницы.

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