Java nie pozwala na wielokrotne dziedziczenie, ale umożliwia implementację wielu interfejsów. Czemu?
Java nie pozwala na wielokrotne dziedziczenie, ale umożliwia implementację wielu interfejsów. Czemu?
Odpowiedzi:
Ponieważ interfejsy określają tylko to, co robi klasa, a nie jak to robi.
Problem z dziedziczeniem wielokrotnym polega na tym, że dwie klasy mogą definiować różne sposoby robienia tego samego, a podklasa nie może wybrać, którą z nich wybrać.
Jeden z moich instruktorów w college'u wyjaśnił mi to w ten sposób:
Załóżmy, że mam jedną klasę, czyli Toster, i inną, czyli NuclearBomb. Oboje mogą mieć ustawienie „ciemności”. Oba mają metodę on (). (Jedna ma off (), druga nie.) Jeśli chcę stworzyć klasę będącą podklasą obu tych ... jak widzisz, jest to problem, który może naprawdę wybuchnąć mi w twarz .
Więc jednym z głównych problemów jest to, że jeśli masz dwie klasy nadrzędne, mogą one mieć różne implementacje tej samej funkcji - lub być może dwie różne funkcje o tej samej nazwie, jak w przykładzie mojego instruktora. Następnie musisz podjąć decyzję, której podklasy będzie używać Twoja podklasa. Z pewnością istnieją sposoby, aby sobie z tym poradzić - robi to C ++ - ale projektanci Javy uznali, że byłoby to zbyt skomplikowane.
Jednak z interfejsem opisujesz coś, co klasa jest w stanie zrobić, zamiast pożyczać metodę innej klasy, aby coś zrobić. Wiele interfejsów znacznie rzadziej powoduje skomplikowane konflikty, które należy rozwiązać, niż wiele klas nadrzędnych.
Ponieważ dziedziczenie jest nadużywane, nawet jeśli nie możesz powiedzieć „hej, ta metoda wygląda na użyteczną, rozszerzę również tę klasę”.
public class MyGodClass extends AppDomainObject, HttpServlet, MouseAdapter,
AbstractTableModel, AbstractListModel, AbstractList, AbstractMap, ...
Odpowiedź na to pytanie leży w wewnętrznym działaniu kompilatora java (łańcuch konstruktora). Jeśli zobaczymy wewnętrzne działanie kompilatora java:
public class Bank {
public void printBankBalance(){
System.out.println("10k");
}
}
class SBI extends Bank{
public void printBankBalance(){
System.out.println("20k");
}
}
Po skompilowaniu wygląda to następująco:
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");
}
}
kiedy rozszerzymy klasę i utworzymy jej obiekt, jeden łańcuch konstruktora będzie działał aż do Object
klasy.
Powyższy kod będzie działał poprawnie. ale jeśli mamy inną klasę o nazwie, Car
która rozszerza Bank
i jedną klasę hybrydową (wielokrotne dziedziczenie) o nazwie 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");
}
}
W tym przypadku (SBICar) nie utworzy łańcucha konstruktora ( niejednoznaczność czasu kompilacji ).
W przypadku interfejsów jest to dozwolone, ponieważ nie możemy stworzyć z niego obiektu.
Aby poznać nową koncepcję default
i static
metodę, zapoznaj się z default w interfejsie .
Mam nadzieję, że to rozwiąże Twoje pytanie. Dzięki.
Implementacja wielu interfejsów jest bardzo przydatna i nie sprawia większych problemów ani implementatorom języka, ani programistom. Więc to jest dozwolone. Wielokrotne dziedziczenie, choć jest również przydatne, może powodować poważne problemy dla użytkowników (straszny diament śmierci ). Większość czynności związanych z dziedziczeniem wielokrotnym można również wykonać za pomocą kompozycji lub klas wewnętrznych. Dlatego zabronione jest dziedziczenie wielokrotne, ponieważ przynosi więcej problemów niż korzyści.
ToyotaCar
i HybridCar
obie wywodziły się z Car
i nadpisały Car.Drive
, i gdyby PriusCar
odziedziczyły oba, ale nie nadpisałybyDrive
, system nie miałby możliwości zidentyfikowania, co Car.Drive
powinien zrobić wirtualny . Interfejsy pozwalają uniknąć tego problemu, unikając powyższego warunku zapisanego kursywą.
void UseCar(Car &foo)
; nie można oczekiwać, że będzie zawierał ujednoznacznienie między ToyotaCar::Drive
i HybridCar::Drive
(ponieważ często nie powinien wiedzieć ani dbać o to, że te inne typy w ogóle istnieją ). Język mógłby, tak jak robi to C ++, wymagać, aby kod, który ToyotaCar &myCar
chce go przekazać, UseCar
musi najpierw rzutować na albo HybridCar
albo ToyotaCar
, ale ponieważ ((Car) (HybridCar) myCar) .Drive` i robiłby ((Car)(ToyotaCar)myCar).Drive
różne rzeczy, co oznaczałoby, że upcasts nie chroniły tożsamości.
Dokładną odpowiedź na to zapytanie można znaleźć na stronie dokumentacji Oracle dotyczącej wielokrotnego dziedziczenia
Wielokrotne dziedziczenie stanu: możliwość dziedziczenia pól z wielu klas
Jednym z powodów, dla których język programowania Java nie pozwala na rozszerzenie więcej niż jednej klasy, jest uniknięcie problemów związanych z wielokrotnym dziedziczeniem stanu, czyli możliwością dziedziczenia pól z wielu klas
Jeśli wielokrotne dziedziczenie jest dozwolone i podczas tworzenia obiektu przez utworzenie instancji tej klasy, obiekt ten odziedziczy pola ze wszystkich nadklas klasy. Spowoduje to dwa problemy.
Wielokrotne dziedziczenie implementacji: Możliwość dziedziczenia definicji metod z wielu klas
Problemy z tym podejściem: konflikty nazw i niejednoznaczność . Jeśli podklasa i nadklasa zawierają tę samą nazwę metody (i podpis), kompilator nie może określić, którą wersję wywołać.
Ale java obsługuje ten typ wielokrotnego dziedziczenia z domyślnymi metodami , które zostały wprowadzone od wydania Java 8. Kompilator Java udostępnia kilka reguł określających, której metody domyślnej używa dana klasa.
Zapoznaj się z poniższym postem SE, aby uzyskać więcej informacji na temat rozwiązywania problemu z diamentami:
Jakie są różnice między klasami abstrakcyjnymi a interfejsami w Javie 8?
Wielokrotne dziedziczenie typu: zdolność klasy do implementacji więcej niż jednego interfejsu.
Ponieważ interfejs nie zawiera zmiennych zmiennych, nie musisz martwić się o problemy wynikające z wielokrotnego dziedziczenia stanu.
Mówi się, że stan obiektów odnosi się do znajdujących się w nim pól i stałoby się niejednoznaczne, gdyby dziedziczono zbyt wiele klas. Tutaj jest link
http://docs.oracle.com/javase/tutorial/java/IandI/multipleinheritance.html
Java obsługuje wielokrotne dziedziczenie tylko przez interfejsy. Klasa może implementować dowolną liczbę interfejsów, ale może rozszerzać tylko jedną klasę.
Dziedziczenie wielokrotne nie jest obsługiwane, ponieważ prowadzi do śmiertelnego problemu z diamentami. Jednak można go rozwiązać, ale prowadzi to do złożonego systemu, więc twórcy Javy porzucili wielokrotne dziedziczenie.
W białej księdze zatytułowanej „Java: an Overview” Jamesa Goslinga w lutym 1995 r. ( Link ) podano, dlaczego dziedziczenie wielokrotne nie jest obsługiwane w Javie.
Według Gosling:
„JAVA pomija wiele rzadko używanych, słabo zrozumiałych, zagmatwanych funkcji C ++, które z naszego doświadczenia przynoszą więcej smutku niż korzyści. Obejmuje to przede wszystkim przeciążanie operatorów (chociaż ma przeciążanie metod), wielokrotne dziedziczenie i rozległe automatyczne wymuszanie”.
Z tego samego powodu C # nie zezwala na wielokrotne dziedziczenie, ale umożliwia implementację wielu interfejsów.
Lekcja wyciągnięta z C ++ z dziedziczeniem wielokrotnym była taka, że prowadziło to do większej liczby problemów, niż było to warte.
Interfejs jest kontraktem rzeczy, które Twoja klasa musi zaimplementować. Nie zyskujesz żadnej funkcjonalności z interfejsu. Dziedziczenie pozwala na dziedziczenie funkcjonalności klasy nadrzędnej (w przypadku dziedziczenia wielokrotnego, co może być bardzo mylące).
Zezwolenie na wiele interfejsów pozwala używać wzorców projektowych (takich jak adapter) do rozwiązywania tych samych typów problemów, które można rozwiązać przy użyciu wielokrotnego dziedziczenia, ale w znacznie bardziej niezawodny i przewidywalny sposób.
D1
i D2
oba dziedziczą po B
i każdy przesłaniają funkcję f
, a jeśli obj
jest wystąpieniem typu, S
który dziedziczy z obu D1
i D2
nie przesłania f
, to rzutowanie odwołania do S
do D1
powinno dać coś, co f
używa D1
przesłonięcia, a rzutowanie na B
nie powinno Zmień to. Podobnie rzutowanie odwołania S
do D2
powinno dać coś, co f
używa D2
override, a rzutowanie na B
nie powinno tego zmieniać. Jeśli język nie musiał pozwalać na dodawanie wirtualnych członków ...
Ponieważ ten temat nie jest zamknięty, opublikuję tę odpowiedź, mam nadzieję, że pomoże to komuś zrozumieć, dlaczego java nie zezwala na wielokrotne dziedziczenie.
Rozważ następującą klasę:
public class Abc{
public void doSomething(){
}
}
W tym przypadku klasa Abc niczego nie rozszerza, prawda? Nie tak szybko, ta klasa niejawnie rozszerza klasę Object, klasę bazową, która pozwala wszystkim działać w Javie. Wszystko jest przedmiotem.
Jeśli spróbujesz użyć klasy powyżej zobaczysz, że Twój IDE pozwalają na stosowanie metod takich jak: equals(Object o)
, toString()
, etc, ale nie deklarują te metody, wyszli z klasy bazowejObject
Możesz spróbować:
public class Abc extends String{
public void doSomething(){
}
}
To jest w porządku, ponieważ twoja klasa nie będzie niejawnie rozszerzać, Object
ale będzie rozszerzać, String
ponieważ to powiedziałeś. Rozważ następującą zmianę:
public class Abc{
public void doSomething(){
}
@Override
public String toString(){
return "hello";
}
}
Teraz Twoja klasa zawsze będzie zwracać „hello”, jeśli wywołasz toString ().
Teraz wyobraź sobie następującą klasę:
public class Flyer{
public void makeFly(){
}
}
public class Bird extends Abc, Flyer{
public void doAnotherThing(){
}
}
Ponownie klasa Flyer
niejawna rozszerza Object, który ma metodę toString()
, każda klasa będzie miała tę metodę, ponieważ wszystkie one rozszerzają się Object
pośrednio, więc jeśli wywołasz toString()
from Bird
, z której toString()
javy musiałby skorzystać? Od Abc
lub Flyer
? Stanie się tak z każdą klasą, która próbuje rozszerzyć dwie lub więcej klas, aby uniknąć tego rodzaju „kolizji metod”, zbudowali ideę interfejsu , w zasadzie można by pomyśleć, że jest to klasa abstrakcyjna, która nie rozszerza Object w sposób pośredni . Ponieważ są abstrakcyjne , będą musiały zostać zaimplementowane przez klasę, która jest obiektem (nie możesz samodzielnie instancjonować interfejsu, muszą być zaimplementowane przez klasę), więc wszystko będzie nadal działać poprawnie.
Aby odróżnić klasy od interfejsów, słowo kluczowe implements zostało zarezerwowane tylko dla interfejsów.
Możesz zaimplementować dowolny interfejs w tej samej klasie, ponieważ domyślnie nic nie rozszerza (ale możesz utworzyć interfejs, który rozszerza inny interfejs, ale ponownie, interfejs „ojca” nie rozszerza Object ”), więc interfejs jest tylko interfejs i nie będą cierpieć z powodu " kolizji sygnatur metod ", jeśli to zrobią, kompilator wyśle ostrzeżenie i będziesz musiał tylko zmienić sygnaturę metody, aby to naprawić (podpis = nazwa metody + parametry + typ zwrotu) .
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
}
Ponieważ interfejs to tylko umowa. Klasa jest w rzeczywistości pojemnikiem na dane.
Na przykład dwie klasy A, B mające tę samą metodę m1 (). A klasa C rozszerza zarówno A, B.
class C extends A, B // for explaining purpose.
Teraz klasa C przeszuka definicję m1. Najpierw wyszuka w klasie, jeśli nie znalazł, a następnie sprawdzi klasę rodziców. Zarówno A, jak i B mają definicję. Więc tutaj pojawia się niejednoznaczność, którą definicję należy wybrać. Więc JAVA NIE WSPIERA WIELU DZIEDZICTWA.
Java nie obsługuje dziedziczenia wielokrotnego z dwóch powodów:
Object
klasy. Gdy dziedziczy z więcej niż jednej superklasy, podklasa uzyskuje niejednoznaczność, aby uzyskać właściwość klasy Object.super()
konstruktora klasy kolacji. Jeśli klasa ma więcej niż jedną superklasę, jest zdezorientowana.Więc gdy jedna klasa pochodzi z więcej niż jednej superklasy, otrzymujemy błąd czasu kompilacji.
Weźmy na przykład przypadek, w którym klasa A ma metodę getSomething, a klasa B ma metodę getSomething, a klasa C rozszerza A i B. Co by się stało, gdyby ktoś nazwał C.getSomething? Nie ma sposobu, aby określić, którą metodę wywołać.
Interfejsy w zasadzie po prostu określają, jakie metody musi zawierać klasa implementująca. Klasa, która implementuje wiele interfejsów, oznacza po prostu, że klasa musi implementować metody ze wszystkich tych interfejsów. Co nie spowoduje żadnych problemów opisanych powyżej.
Rozważmy scenariusz, w którym Test1, Test2 i Test3 to trzy klasy. Klasa Test3 dziedziczy klasy Test2 i Test1. Jeśli klasy Test1 i Test2 mają tę samą metodę i wywołasz ją z obiektu klasy potomnej, wywołanie metody klasy Test1 lub Test2 będzie niejednoznaczne, ale nie ma takiej niejednoznaczności dla interfejsu, jak w interfejsie nie ma implementacji.
Java nie obsługuje dziedziczenia wielokrotnego, wielościeżkowego i hybrydowego z powodu problemu z niejednoznacznością:
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.
Link referencyjny: https://plus.google.com/u/0/communities/102217496457095083679
w prosty sposób wszyscy wiemy, możemy dziedziczyć (rozszerzać) jedną klasę, ale możemy zaimplementować tak wiele interfejsów ... to dlatego, że w interfejsach nie podajemy implementacji, po prostu mówimy o funkcjonalności. załóżmy, że jeśli java może rozszerzyć tak wiele klas, a te mają te same metody .. w tym miejscu, jeśli spróbujemy wywołać metodę superklasy w podklasie, jaką metodę ma uruchomić?, kompilator jest zagmatwany przykład: - spróbuj wielu rozszerzeń, ale w interfejsy te metody nie mają treści, powinniśmy zaimplementować je w podklasie .. spróbuj użyć wielu implementacji, więc nie martw się.
* To prosta odpowiedź, ponieważ jestem początkującym użytkownikiem języka Java *
Rozważmy istnieją trzy klasy X
, Y
a Z
.
Więc dziedziczymy jak X extends Y, Z
I oba Y
i Z
mamy metodę alphabet()
z tym samym typem zwracania i argumentami. Ta metoda alphabet()
w Y
powiada wyświetlić pierwszy alfabet i metody alfabetu w Z
mówi wyświetlania ostatniego alfabetu . Tak więc pojawia się niejednoznaczność, gdy alphabet()
jest wywoływany przez X
. Czy mówi się o wyświetlaniu pierwszego czy ostatniego alfabetu ??? Dlatego java nie obsługuje dziedziczenia wielokrotnego. W przypadku interfejsów rozważ Y
i Z
jako interfejsy. Więc oba będą zawierać deklarację metody, alphabet()
ale nie definicję. Nie powie, czy wyświetlić pierwszy czy ostatni alfabet, czy cokolwiek, ale po prostu zadeklaruje metodęalphabet()
. Nie ma więc powodu, by podnosić tę niejednoznaczność. Możemy zdefiniować metodę za pomocą dowolnego elementu wewnątrz klasy X
.
Krótko mówiąc, definicja interfejsów jest wykonywana po implementacji, więc nie ma zamieszania.