Dlaczego Java nie obsługuje wielodziedziczenia klas
Część sekcji Programowanie obiektowe ścieżki Java w Coddy. Lekcja 26 z 87.
Java celowo uniemożliwia klasie dziedziczenie po więcej niż jednej klasie. To ograniczenie wynika z problemu znanego jako problem diamentu.
Wyobraź sobie, że Java pozwala na wielokrotne dziedziczenie. Rozważmy ten hipotetyczny scenariusz:
// Ten kod NIE jest poprawny w Java
public class A {
public void greet() {
System.out.println("Hello from A");
}
}
public class B extends A {
@Override
public void greet() {
System.out.println("Hello from B");
}
}
public class C extends A {
@Override
public void greet() {
System.out.println("Hello from C");
}
}
// NIEDOZWOLONE w Java!
public class D extends B, C { }Jeśli wywołasz greet() na obiekcie D, która wersja powinna się uruchomić? Ta z B czy z C? Ta niejednoznaczność to problem diamentu, nazwany tak ze względu na kształt diamentu utworzony przez diagram dziedziczenia.
Rozwiązanie w Javie jest proste: zezwolić na dziedziczenie tylko po jednej klasie. Java oferuje jednak alternatywę. Klasa może implementować wiele interfejsów, o których dowiesz się w jednym z kolejnych rozdziałów.
Interfejsy rozwiązują problem diamentowy w inny sposób i zapewniają elastyczność bez niejednoznaczności.
// To JEST poprawne w Java
public class Dog extends Animal implements Runnable, Comparable<Dog> {
// Może implementować wiele interfejsów
}Ten wybór projektowy sprawia, że model dziedziczenia w Javie pozostaje przejrzysty i przewidywalny, a jednocześnie umożliwia ponowne wykorzystanie kodu za pomocą interfejsów.
Wyzwanie
ŁatwyZbudujmy system odtwarzacza muzyki, który demonstruje zasadę pojedynczego dziedziczenia w Javie i pokazuje, jak mimo tego ograniczenia można tworzyć elastyczne projekty.
Wyobraź sobie, że chcesz stworzyć inteligentny głośnik łączący funkcje odtwarzacza muzyki i asystenta głosowego. Ponieważ Java nie pozwala dziedziczyć po wielu klasach, musisz starannie zaprojektować hierarchię, korzystając z pojedynczego dziedziczenia.
Uporządkujesz kod w czterech plikach:
AudioDevice.java: Utwórz klasę bazową wspólną dla wszystkich urządzeń odtwarzających dźwięk. Powinna zawierać:- Prywatne pole
deviceName(String) - Konstruktor przyjmujący nazwę urządzenia
- Metodę
getDeviceName(), która zwraca nazwę - Metodę
playSound(), która wyświetla:[deviceName]: Playing audio
- Prywatne pole
MusicPlayer.java: Utwórz klasę dziedziczącą po AudioDevice i dodającą funkcje związane z muzyką:- Prywatne pole
currentSong(String) - Konstruktor przyjmujący nazwę urządzenia i używający
super(deviceName) - Metodę
setSong(String song), która ustawia bieżący utwór - Przesłoń
playSound(), aby wyświetlała:[deviceName]: Now playing - [currentSong]
- Prywatne pole
SmartSpeaker.java: Utwórz klasę dziedziczącą po MusicPlayer (a nie jednocześnie po MusicPlayer i jakiejś klasie VoiceAssistant — to byłoby dziedziczenie wielokrotne!). Inteligentny głośnik rozszerza możliwości odtwarzacza muzyki o funkcje głosowe:- Prywatne pole
assistantName(String) - Konstruktor przyjmujący nazwę urządzenia i nazwę asystenta oraz używający
super(deviceName) - Metodę
voiceCommand(String command), która wyświetla:[assistantName]: Processing "[command]" - Metodę
getFullInfo(), która wyświetla:[deviceName] with [assistantName] assistant
- Prywatne pole
Main.java: Zademonstruj swój łańcuch dziedziczenia wielopoziomowego. Otrzymasz trzy dane wejściowe: nazwę urządzenia (String), nazwę asystenta (String) i nazwę utworu (String). Utwórz obiekt SmartSpeaker, ustaw utwór, a następnie wywołaj kolejno te metody:getFullInfo()playSound()voiceCommand("skip to next")
Otrzymasz trzy dane wejściowe: nazwę urządzenia, nazwę asystenta i nazwę utworu.
Zwróć uwagę, jak SmartSpeaker zyskuje możliwości odtwarzania muzyki, dziedzicząc po MusicPlayer w łańcuchu (AudioDevice -> MusicPlayer -> SmartSpeaker), zamiast próbować dziedziczyć po wielu niepowiązanych klasach. Tak Java dba o to, by dziedziczenie było przejrzyste i jednoznaczne!
Spróbuj swoich sił
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Wczytaj dane wejściowe
String deviceName = scanner.nextLine();
String assistantName = scanner.nextLine();
String songName = scanner.nextLine();
// TODO: Utwórz SmartSpeaker z deviceName i assistantName
// TODO: Ustaw utwór za pomocą setSong()
// TODO: Wywołaj getFullInfo()
// TODO: Wywołaj playSound()
// TODO: Wywołaj voiceCommand("skip to next")
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneWprowadzenie do programowania obiektowegoKlasy a obiektySłowo kluczowe thisMetodyPola (atrybuty)Metoda konstruktoraPrzeciążanie konstruktoraPodsumowanie — prosty kalkulator4Dziedziczenie
Podstawy dziedziczenia (extends)Słowo kluczowe superPrzesłanianie metod (@Override)Łańcuch wywołań konstruktorówKlasa ObjectDziedziczenie jedno- i wielopoziomoweDlaczego Java nie obsługuje wielodziedziczenia klasPodsumowanie – hierarchia pracowników7Metody specjalne i klasa Object
Metoda toString()equals() i hashCode()Metoda clone()compareTo() i ComparableInterfejs ComparatorPodsumowanie — własne sortowanie10Obsługa wyjątków w programowaniu obiektowym
Hierarchia klas wyjątkówWłasne wyjątkiBłędy sprawdzane a niesprawdzaneWzorzec try-with-resourcesPodsumowanie – zweryfikowany użytkownik2Modyfikatory dostępu i enkapsulacja
Przegląd poziomów dostępuMetody pobierające i ustawiająceUkrywanie informacjiSłowo kluczowe finalPodsumowanie — menedżer kont bankowych5Polimorfizm
Podstawy przeciążania metodPrzesłanianie metod (w czasie wykonywania)Rzutowanie w górę i w dółOperator instanceofKlasy i metody abstrakcyjnePowtórka — kalkulator figur8Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieAgregacja a kompozycjaKlasy wewnętrzne, zagnieżdżone i anonimoweEnumy i metody enumówRekordy (Java 16+)Klasy zapieczętowane (Java 17+)11Wzorce projektowe — część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec BuilderWzorzec ObserverWzorzec Strategy3Właściwości klas i składowe statyczne
Zmienne instancji a zmienne statyczneMetody statyczneBloki statyczneStałe (static final)Powtórka – Counter i Utility6Interfejsy i klasy abstrakcyjne
Wprowadzenie do interfejsówImplementowanie interfejsówImplementacja wielu interfejsówMetody domyślne i statyczne w interfejsieKlasy abstrakcyjne a interfejsyInterfejsy funkcyjnePodsumowanie — system płatnościPoćwicz samodzielnie: Kompilator Java online