Чому на Java не існує багатократного успадкування, але допускається реалізація декількох інтерфейсів?


153

Java не допускає багаторазове успадкування, але дозволяє реалізувати декілька інтерфейсів. Чому?


1
Я відредагував назву питання, щоб зробити його більш описовим.
Божо

4
Цікаво, що в JDK 8 існують методи розширення, які дозволять визначити реалізацію методів інтерфейсу. Визначено правила, що регулюють багатократне успадкування поведінки, але не державного (що я розумію, є більш проблематичним .
Едвін Далорцо,

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

Відповіді:


228

Тому що інтерфейси задають лише те, що робить клас, а не те, як він це робить.

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


8
Раніше я робив C ++ і натрапляв на цю саму проблему зовсім кілька разів. Нещодавно я читав про те, що у Scala є "риси", які мені здаються щось середнє між способом "C ++" та "Java".
Niels Basjes

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. Вони можуть мати налаштування "темряви". Вони обидва мають метод (). (У одного вимкнено (), у іншого немає.) Якщо я хочу створити клас, який є підкласом обох цих ... як ви бачите, це проблема, яка справді може зірватися мені в обличчя .

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

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


5
Спасибі, NomeN Джерелом цитати був аспірант на ім'я Брендан Бернс, який у той час також був обслуговуючим сховищем джерел Quake 2 з відкритим кодом. Піди розберися.
Синтаксичний

4
Проблема цієї аналогії полягає в тому, що якщо ви створюєте підклас ядерної бомби та тостеру, "ядерний тостер" розумно підірветься при використанні.
101100

20
Якщо хтось вирішить змішати ядерну бомбу і тостер, він заслуговує на те, щоб бомба вибухнула йому в обличчя. Цитата є помилковим міркуванням
масуд

1
Одна і та ж мова програмування дає змогу успадковувати декілька інтерфейсів, тому це "обгрунтування" не застосовується.
curiousguy

24

Оскільки спадкування надмірно використовується навіть тоді, коли ви не можете сказати "ей, цей метод виглядає корисним, я також продовжую цей клас".

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

Чи можете ви пояснити, чому ви кажете, що надбання надмірно використовується? Створення класу бога - саме те, що я хочу зробити! Я знаходжу так багато людей, які працюють навколо одного успадкування, створюючи класи "Інструменти", які мають статичні методи.
Duncan Calvert

9
@DuncanCalvert: Ні, ви не хочете цього робити, не якщо цей код колись потребуватиме обслуговування. Багато статичних методів не вистачає точки OO, але надмірне багаторазове успадкування набагато гірше, оскільки ви повністю втрачаєте інформацію про те, який код використовується, а також, що таке клас концептуально. Обидва намагаються вирішити проблему "як я можу використовувати цей код там, де мені це потрібно", але це проста короткочасна проблема. Набагато складніше довгострокова проблема, вирішена правильним дизайном ОО, - це "як я можу змінити цей код, не маючи перерви в 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

Реалізація декількох інтерфейсів дуже корисна і не створює особливих проблем ні мовникам, ні програмістам. Так дозволено. Багатократне успадкування, хоча і корисне, може спричинити серйозні проблеми для користувачів (страшний діамант смерті ). І більшість речей, які ви робите з багаторазовим успадкуванням, можна зробити також складом або використанням внутрішніх класів. Тож багаторазове успадкування заборонено, оскільки приносить більше проблем, ніж прибуток.


У чому проблема з "діамантом смерті"?
curiousguy

2
@curiousguy, що містить більше одного субекти одного базового класу, неоднозначність (переосмислення використання базового класу), складні правила вирішення такої неоднозначності.
Тадеуш Копец

@curiousguy: Якщо рамка передбачає, що кастинг посилання на об'єкт на посилання базового типу буде збереженням ідентичності, то кожен екземпляр об'єкта повинен мати точно одну реалізацію будь-якого методу базового класу. Якби ToyotaCarі HybridCarотримувались, Carі переохоплювались Car.Drive, і якщо PriusCarуспадковувались обоє, але не переосмислювалисьDrive , система не мала б можливості визначити, що Car.Driveмає робити віртуальний . Інтерфейси уникають цієї проблеми, уникаючи наведеного вище курсивного стану.
supercat

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

1
@ChrisMiddleton: метод void UseCar(Car &foo); не можна очікувати, що вони включатимуть розбіжність між ToyotaCar::Driveта HybridCar::Drive(оскільки воно часто не повинно ні знати, ні дбати про те, що ці інші типи навіть існують ). Мова може, як і C ++, вимагати, щоб цей код із ToyotaCar &myCarбажанням передати його UseCarпотрібно спочатку передавати або на, HybridCarабо ToyotaCar, але оскільки ((Автомобіль) (HybridCar) myCar) .Drive` і ((Car)(ToyotaCar)myCar).Drive робив би різні речі, це означало б, що оновлення не зберігали ідентичність
supercat

6

Ви можете знайти точну відповідь на цей запит на сторінці документації oracle про багаторазове успадкування

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

    Однією з причин, чому мова програмування Java не дозволяє вам поширювати більше ніж один клас, - це уникати питань багаторазового успадкування стану, що полягає у можливості успадкування полів з декількох класів

    Якщо дозволено багатократне успадкування, і коли ви створюєте об'єкт шляхом інстанціювання цього класу, цей об'єкт успадкує поля з усіх суперкласів класу. Це спричинить два питання.

    1. Що робити, якщо методи або конструктори з різних суперкласів створюють одне і те ж поле?
    2. Який метод чи конструктор матиме перевагу?
  2. Множинне успадкування реалізації: Можливість успадкування визначень методу з декількох класів

    Проблеми з таким підходом: конфлікт назви та неоднозначність . Якщо підклас та надклас містять однакове ім'я методу (та підпис), компілятор не може визначити, до якої версії слід звертатися.

    Але java підтримує цей тип багаторазового успадкування методами за замовчуванням , які були запроваджені з моменту випуску Java 8. Компілятор Java надає деякі правила для визначення методу за замовчуванням для конкретного класу.

    Детальнішу інформацію щодо вирішення проблеми з алмазами див. У розділі SE внизу:

    Які відмінності між абстрактними класами та інтерфейсами в Java 8?

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

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



4

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

Багаторазове успадкування не підтримується, оскільки це призводить до смертельної проблеми з алмазами. Однак це може бути вирішено, але це призводить до складної системи, тому засновники Java відмовилися від багаторазового успадкування.

У Білій книзі під назвою «Java: огляд» Джеймса Гослінга в лютому 1995 року ( посилання ) дається уявлення про те, чому багатоточне успадкування не підтримується на Java.

За словами Гослінга:

"JAVA опускає багато рідко використовуваних, погано зрозумілих, заплутаних особливостей C ++, які, за нашим досвідом, приносять більше горя, ніж користі. Це, насамперед, перевантаження оператора (хоча у нього є перевантаження методом), багаторазового успадкування та широких автоматичних примусів".


посилання недоступне. Просимо перевірити та оновити його.
Машукхан

3

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

Урок, отриманий з C ++ w / багаторазового успадкування, полягав у тому, що це призвело до більшої кількості питань, ніж варто.

Інтерфейс - це договір речей, які ваш клас повинен реалізувати. Ви не отримуєте жодної функціональності від інтерфейсу. Спадщина дозволяє успадкувати функціональність батьківського класу (і при множинному успадкуванні, що може отримати надзвичайно заплутаність).

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


10
C # не має багаторазового успадкування саме тому, що Java не дозволяє. Він був розроблений набагато пізніше, ніж Java. Головною проблемою багаторазового успадкування, на мою думку, було те, як людей вчили користуватися ним ліворуч та праворуч. Концепція, що делегація в більшості випадків є набагато кращою альтернативою, просто не існувала на початку та середині дев'яностих. Отже, я пам’ятаю приклади в підручниках, коли Автомобіль - це колесо, двері та лобове скло, проти автомобіля - колеса, двері та лобове скло. Тож єдине успадкування на Яві було реакцією на колінну ривку.
Олександр Погребняк

2
@AlexanderPogrebnyak: Оберіть два з наступних трьох: (1) Дозволити збереження ідентичності відліків від посилання підтипу до посилання на супертип; (2) Дозволити класу додавати віртуальних публічних членів без перекомпіляції похідних класів; (3) Дозволити класу неявно успадковувати віртуальні члени з декількох базових класів без необхідності чітко їх вказувати. Я не вірю, що будь-якою мовою можна керувати всіма трьома вищезазначеними. Java обрала №1 та №2, а наступний C #. Я вважаю, що C ++ приймає лише №3. Особисто я вважаю, що №1 і №2 корисніші, ніж №3, але інші можуть відрізнятися.
supercat

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

@TheParamagneticCroissant: Основна семантична проблема - №1. Якщо D1і D2обидва успадковують з B, і кожен переосмислює функцію f, і якщо objце екземпляр типу, Sякий успадковує і те, D1і інше, D2але не переосмислює f, тоді при передачі посилання на Sце D1слід отримувати те, що fвикористовує D1переопределення, а кастинг не Bповинен змінити це. Аналогічно, при передаванні посилання Sна нього D2слід отримувати те, що fвикористовує D2переопределення, а лиття не Bповинно цього змінювати. Якщо мові не потрібно було дозволити додавання віртуальних членів ...
supercat

1
@TheParamagneticCroissant: Це могло, і мій список варіантів був дещо надмірно спрощений, щоб увійти в коментар, але відкладання часу на виконання не дозволяє автору D1, D2 або S знати, які зміни вони можуть внести, не порушуючи споживачі свого класу.
supercat

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";
    }

}

Тепер ваш клас завжди повернеться "привіт", якщо ви зателефонуєте доString ().

Тепер уявіть собі наступний клас:

public class Flyer{

    public void makeFly(){

    }

}

public class Bird extends Abc, Flyer{

    public void doAnotherThing(){

    }

}

Знову клас Flyerнеявно розширює Об'єкт, у якого є метод toString(), у будь-якого класу буде цей метод, оскільки всі вони поширюються Objectопосередковано, тому, якщо ви телефонуєте toString()зBird , який toString()Java повинен був би використовувати? З Abcабо Flyer? Це станеться з будь-яким класом, який намагається розширити два або більше класів, щоб уникнути подібного «зіткнення методу», в якому вони побудували ідею інтерфейсу , в основному ви могли б вважати їх абстрактним класом, який не розширює об’єкт опосередковано . Оскільки вони абстрактні, їх доведеться реалізувати класом, який є об'єктом (інтерфейс не можна створити самостійно, він повинен бути реалізований класом), тому все буде працювати нормально.

Щоб відрізняти класи від інтерфейсів, ключові слова реалізовано лише для інтерфейсів.

Ви можете реалізувати будь-який інтерфейс, який вам подобається, в тому ж класі, оскільки він за замовчуванням нічого не розширює (але ви можете створити інтерфейс, який розширює інший інтерфейс, але знову ж таки, "батьковий" інтерфейс не поширює "Об'єкт"), тому інтерфейс є просто інтерфейс, і вони не страждатимуть від " методів зіткнення підписів ", якщо вони виконають компілятор, викличуть вам попередження, і вам доведеться просто змінити підпис методу, щоб виправити це (підпис = ім'я методу + парами + тип повернення) .

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

Тому що інтерфейс - це лише договір. І клас насправді є контейнером для даних.


Основна складність при багатократному успадкуванні полягає в можливості того, що клас може успадкувати члена за допомогою декількох шляхів, які реалізують його по-різному, не надаючи власної переважної реалізації. Успадковування інтерфейсу уникає цього, оскільки єдині учасники інтерфейсу можуть бути реалізовані в класах, нащадки яких обмежуватимуться єдиним успадкуванням.
supercat

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. У Яві кожен клас - дитина дитини Object класу. Коли він успадковується від більш ніж одного суперкласу, підклас отримує неоднозначність для придбання властивості класу Object ..
  2. У java у кожного класу є конструктор, якщо ми пишемо це явно чи взагалі немає. Перше твердження викликає виклик super()конструктора класу supper. Якщо в класі є більше одного суперкласу, він плутається.

Отже, коли один клас поширюється на більш ніж один суперклас, ми отримуємо помилку часу компіляції.


0

Візьмемо для прикладу випадок, коли клас A має метод getSomething, а клас B метод getSomething, а клас C розширює A і B. Що буде, якщо хтось назвав C.getSomething? Немає способу визначити, який метод викликати.

В основному інтерфейси просто вказують, які методи повинен містити клас реалізації. Клас, який реалізує кілька інтерфейсів, означає, що клас повинен реалізувати методи з усіх цих інтерфейсів. Whci не призведе до жодних проблем, як описано вище.


2
" Що станеться, якщо хтось назвав C.getSomething ". Це помилка в C ++. Проблема вирішена.
curiousguy

Це був сенс ... це був протилежний приклад, який я вважав зрозумілим. Я вказував, що немає способу визначити, який спосіб отримати щось. Також в якості побічної ноти, питання стосувалося java not c ++
Джон Кейн

Мені дуже шкода, що я не розумію, що ви думаєте. Очевидно, в деяких випадках із ІМ є неоднозначність. Як це зустрічний приклад? Хто стверджував, що ІМ ніколи не призводить до неоднозначності? " Також в якості побічної ноти, питання стосувалося java not c ++ " Так?
допитливий хлопець

Я просто намагався показати, чому ця двозначність існує і чому вона не відповідає інтерфейсам.
Джон Кейн

Так, MI може спричинити неоднозначні дзвінки. Так може бути перевантаження. Тож Java повинна зняти перевантаження?
curiousguy

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 І, так 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.