Abstrakte Klassen vs. Interfaces
Teil des Abschnitts Objektorientierte Programmierung der Java-Journey von Coddy. Lektion 38 von 87.
Da du nun sowohl abstrakte Klassen als auch Schnittstellen verstehst, stellt sich eine häufige Frage: Wann solltest du die eine der anderen vorziehen? Die Wahl hängt davon ab, was du modellieren möchtest.
Verwende eine abstrakte Klasse, wenn Klassen eine gemeinsame Basisklasse mit gemeinsamem Zustand oder Verhalten haben. Abstrakte Klassen können Instanzvariablen, Konstruktoren und eine Mischung aus abstrakten und konkreten Methoden enthalten:
public abstract class Animal {
protected String name; // gemeinsamer Zustand
public Animal(String name) {
this.name = name;
}
public void sleep() { // gemeinsames Verhalten
System.out.println(name + " is sleeping");
}
public abstract void makeSound(); // muss implementiert werden
}Verwende eine Schnittstelle, wenn du eine Fähigkeit definieren möchtest, die nicht verwandte classes gemeinsam haben can. Schnittstellen konzentrieren sich darauf, was ein Objekt tun can, nicht darauf, was es ist:
public interface Flyable {
void fly();
}
// Unverwandte Klassen können diese Fähigkeit teilen
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }Hier ist ein schneller Vergleich:
| Funktion | Abstrakte Klasse | Schnittstelle |
|---|---|---|
| Instanzvariablen | Ja | Nur Konstanten |
| Konstruktoren | Ja | Nein |
| Mehrfachvererbung | Nein (einfaches extends) | Ja (mehrere implements) |
| Zugriffsmodifikatoren | Beliebige | Nur public (für abstrakte Methoden) |
Eine praktische Richtlinie: Wenn du eine abstrakte Klasse mit ausschließlich abstrakten Methoden und ohne Zustand erstellst, ist eine Schnittstelle wahrscheinlich die bessere Wahl.
Aufgabe
EinfachErstellen wir ein Fahrzeugsystem, das veranschaulicht, wann abstrakte Klassen und wann Schnittstellen verwendet werden. Du modellierst Fahrzeuge, die gemeinsamen Zustand und gemeinsames Verhalten über eine abstrakte Klasse teilen, während optionale Fähigkeiten über Schnittstellen hinzugefügt werden.
Du wirst deinen Code auf fünf Dateien verteilen:
Vehicle.java: Erstelle eine abstrakte Klasse, die als Grundlage für alle Fahrzeuge dient. Jedes Fahrzeug hat ein Feldbrand(String) und ein Feldyear(int). Füge einen Konstruktor hinzu, der beide Felder initialisiert, sowie Getter-Methoden für jedes Feld und eine abstrakte MethodestartEngine(), die einen String zurückgibt. Füge außerdem eine konkrete MethodegetInfo()hinzu, die Folgendes zurückgibt:[brand] ([year]). Dies ist ein perfekter Anwendungsfall für eine abstrakte Klasse: Fahrzeuge teilen sich Zustand (brand, year) und bestimmtes Verhalten (getInfo), aber jedes Fahrzeug startet seinen Motor auf andere Weise.Convertible.java: Definiere eine Schnittstelle für Fahrzeuge, die ihr Dach öffnen und schließen können. Diese Fähigkeit hängt nicht davon ab, was ein Fahrzeug ist: Es geht um etwas, das bestimmte Fahrzeuge tun können. Deklariere zwei Methoden:openRoof()undcloseRoof(), die beide einen String zurückgeben.Car.java: Erstelle eine Klasse, dieVehicleerweitert undConvertibleimplementiert. Ein Car hat ein zusätzliches FeldnumDoors(int). Verwendesuper, um die geerbten Felder zu initialisieren. ImplementierestartEngine()so, dass[brand] car engine startedzurückgegeben wird. ImplementiereopenRoof()so, dass[brand] roof openingzurückgegeben wird, undcloseRoof()so, dass[brand] roof closingzurückgegeben wird.Motorcycle.java: Erstelle eine Klasse, dieVehicleerweitert, aber nichtConvertibleimplementiert: Motorräder haben kein Dach! Ein Motorcycle hat ein FeldhasSidecar(boolean). ImplementierestartEngine()so, dass[brand] motorcycle engine roaringzurückgegeben wird.Main.java: Führe alles zusammen, um den Unterschied zwischen abstrakten Klassen und Schnittstellen zu veranschaulichen. Du erhältst vier Eingaben: eine Automarke, ein Autojahr, eine Motorradmarke und ein Motorradjahr.Erstelle ein Car (mit 4 Türen) und ein Motorcycle (ohne Beiwagen). Zeige zuerst das gemeinsame Verhalten der abstrakten Klasse, indem du
getInfo()für beide Fahrzeuge ausgibst. Beide erben diese Methode von Vehicle. Gib anschließend das Ergebnis vonstartEngine()für jedes Fahrzeug aus: Beachte, wie jeder Fahrzeugtyp dies unterschiedlich implementiert.Zeige schließlich die Fähigkeit der Schnittstelle: Da nur das Car
Convertibleimplementiert, rufe nur für das AutoopenRoof()auf und gib das Ergebnis aus, gefolgt voncloseRoof().
Du erhältst vier Eingaben: Automarke (String), Autojahr (int), Motorradmarke (String) und Motorradjahr (int).
Deine Ausgabe sollte insgesamt sechs Zeilen enthalten. Beachte, wie beide Fahrzeuge den Zustand der abstrakten Klasse und deren Methode getInfo() gemeinsam nutzen, aber nur das Car die Fähigkeit zum Öffnen und Schließen des Dachs besitzt. Dies veranschaulicht, wann welcher Ansatz verwendet werden sollte!
Probier es selbst
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Eingaben lesen
String carBrand = scanner.nextLine();
int carYear = scanner.nextInt();
scanner.nextLine(); // Zeilenumbruch konsumieren
String motorcycleBrand = scanner.nextLine();
int motorcycleYear = scanner.nextInt();
// TODO: Erstelle ein Car mit 4 Türen
// TODO: Erstelle ein Motorcycle ohne Beiwagen (false)
// TODO: Gib getInfo() für beide Fahrzeuge aus (demonstriert gemeinsames Verhalten der abstrakten Klasse)
// TODO: Gib startEngine() für beide Fahrzeuge aus (demonstriert unterschiedliche Implementierungen)
// TODO: Gib openRoof() und closeRoof() nur für das Auto aus (demonstriert Interface-Fähigkeit)
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienEinführung in die OOPKlassen vs. ObjekteDas Schlüsselwort thisMethodenFelder (Attribute)KonstruktormethodeÜberladen von KonstruktorenRückblick – Einfacher Taschenrechner4Vererbung
Grundlegende Vererbung (extends)Das Schlüsselwort superÜberschreiben von Methoden (@Override)KonstruktorverkettungDie Object-KlasseEinfach- und mehrstufige VererbungWarum keine Mehrfachvererbung von KlassenRückblick – Mitarbeiterhierarchie7Spezielle Methoden & Object-Klasse
toString()-Methodeequals() und hashCode()clone()-MethodecompareTo() und ComparableComparator-SchnittstelleRückblick – Benutzerdefinierte Sortierung2Zugriffsmodifikatoren & Kapselung
Übersicht der ZugriffsebenenGetter- und Setter-MethodenInformationsverbergungDas Schlüsselwort finalRückblick – Bankkontoverwaltung5Polymorphismus
Grundlagen der MethodenüberladungMethodenüberschreibung (Laufzeit)Upcasting und DowncastingDer instanceof-OperatorAbstrakte Klassen und MethodenRückblick – Formrechner8Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungAggregation vs. KompositionInnere, verschachtelte & anonyme KlassenEnums und Enum-MethodenRecords (Java 16+)Versiegelte Klassen (Java 17+)11Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterBuilder-MusterObserver-MusterStrategie-Muster14Abschließende Herausforderungen
E-Learning-PlattformBankingsystemEntwicklung einer SpielfigurFahrzeugvermietungsdienst3Klassen-Props & statische Member
Instanz- vs. statische VariablenStatische MethodenStatische BlöckeKonstanten (static final)Wiederholung – Counter & Utility6Schnittstellen & abstrakte Klassen
Einführung in InterfacesInterfaces implementierenMehrere Interfaces implementierenDefault & Static in InterfacesAbstrakte Klassen vs. InterfacesFunktionale InterfacesRückblick – ZahlungssystemÜbe selbstständig: Online-Java-Compiler