- 1 Section
- 10 Lessons
- unbegrenzt
- Java Fortgeschritten10
- 1.1OOP in Java: Klassen, Kapselung, Konstruktoren
- 1.2Vererbung in Java
- 1.3Polymorphismus und Methodenüberschreibung
- 1.4Interfaces und abstrakte Klassen
- 1.5Collections: ArrayList, HashMap, LinkedList
- 1.6Generics und Typsicherheit
- 1.7Streams und Lambda-Ausdrücke
- 1.8Dateioperationen: IO und NIO
- 1.9Unit-Tests in Java: JUnit 5
- 1.10Praxisprojekt: Anwendung mit OOP und Tests
Interfaces und abstrakte Klassen
In L2 (Vererbung) hast du extends kennengelernt und in L3 auch schon eine abstrakte Klasse gesehen. Diese Lektion vertieft das: was sind abstrakte Klassen genau, was sind Interfaces und wann nimmt man was?
Beide Konzepte definieren Verträge: „Wer dies sein will, muss diese Methoden anbieten." Sie sind das Rückgrat moderner Java-Architekturen – Frameworks wie Spring, JPA und Servlets bauen massiv darauf auf. Außerdem: ein Java-Interface ist der wichtigste Hebel, um Mehrfachvererbung zu erreichen, die Java für Klassen sonst verbietet.
1) Was ist ein Interface?
Ein Interface ist im Kern eine Liste von Methoden ohne Implementierung. Es sagt: „Wer mich implementieren will, muss diese Methoden anbieten."
public interface Druckbar { void drucken(); int getSeitenzahl(); }
Eine Klasse implementiert ein Interface mit implements und muss alle Methoden konkret ausprogrammieren:
public class Rechnung implements Druckbar { @Override public void drucken() { System.out.println("Rechnung gedruckt"); } @Override public int getSeitenzahl() { return 2; } }
Jetzt kann Rechnung überall verwendet werden, wo eine Druckbar erwartet wird:
Druckbar d = new Rechnung(); d.drucken();
2) Was ist eine abstrakte Klasse?
Eine abstrakte Klasse liegt zwischen einer normalen Klasse und einem Interface. Sie kann:
- Konkrete Methoden mit Implementierung haben
- Abstrakte Methoden haben, die Unterklassen implementieren müssen
- Felder, Konstruktoren und Sichtbarkeits-Modifier nutzen wie eine normale Klasse
- Aber nicht direkt instanziiert werden
public abstract class Dokument { protected String titel; public Dokument(String titel) { this.titel = titel; } // konkrete Methode – wird vererbt public String getTitel() { return titel; } // abstrakte Methode – muss überschrieben werden public abstract void drucken(); } public class Rechnung extends Dokument { public Rechnung(String titel) { super(titel); } @Override public void drucken() { System.out.println("Drucke: " + titel); } }
3) Der direkte Vergleich
- Definiert nur Verhalten (was muss möglich sein)
- Eine Klasse kann mehrere Interfaces implementieren
- Methoden sind implizit
public abstract - Felder sind implizit
public static final(= Konstanten) - Kein Konstruktor
- Seit Java 8:
default-Methoden mit Implementierung möglich - Mit
implementsverwendet
- Definiert Verhalten + Zustand
- Eine Klasse kann nur von einer abstrakten Klasse erben
- Methoden können konkret oder abstrakt sein
- Felder mit beliebiger Sichtbarkeit
- Konstruktor möglich (für Unterklassen)
- Kann teilweise implementiert sein
- Mit
extendsverwendet
4) Wann was wählen?
Faustregeln, die in 90 % der Fälle passen:
- Interface, wenn du nur einen Vertrag definieren willst („wer ist druckbar?"), oder wenn Klassen aus verschiedenen Hierarchien dieselbe Fähigkeit haben sollen
- Abstrakte Klasse, wenn es eine echte Vererbungs-Hierarchie mit gemeinsamem Zustand und gemeinsamem Code gibt („alle Dokumente haben einen Titel und können drucken")
- Im Zweifel Interface: flexibler, mehrfach kombinierbar, mit Mock-Implementierungen leichter testbar
5) Mehrfache Interfaces implementieren
Java erlaubt nur eine direkte Oberklasse – aber eine Klasse kann beliebig viele Interfaces implementieren. Das ist der Trick, um Mehrfachvererbung zu umgehen:
public interface Druckbar { void drucken(); } public interface Speicherbar { void speichern(); } public interface Versendbar { void versenden(); } public class Rechnung implements Druckbar, Speicherbar, Versendbar { @Override public void drucken() { /* ... */ } @Override public void speichern() { /* ... */ } @Override public void versenden() { /* ... */ } }
Druckbar
Speicherbar
Versendbar
Eine Liste List<Druckbar> kann Rechnungen aufnehmen, eine List<Speicherbar> ebenfalls – derselbe Code, verschiedene Sichten. Genau dieses Muster trifft man in Frameworks ständig.
6) Default-Methoden in Interfaces
Seit Java 8 dürfen Interfaces auch Standardimplementierungen haben. So lassen sich Methoden zu bestehenden Interfaces hinzufügen, ohne alle implementierenden Klassen anzupassen:
public interface Druckbar { void drucken(); // Default-Methode mit Implementierung default void druckenMitKopf() { System.out.println("=== KOPF ==="); drucken(); System.out.println("=== FUSS ==="); } }
Wer Druckbar implementiert, kann druckenMitKopf() nutzen, ohne sie selbst auszuprogrammieren. Bei Bedarf lässt sich die Default-Implementierung aber überschreiben.
7) Die drei Methodenarten in Interfaces
abstract kann man weglassen.Druckbar.factory().8) Interface-Mehrfachvererbung
Interfaces können von mehreren anderen Interfaces erben:
public interface Dokument extends Druckbar, Speicherbar { String getTitel(); } public class Rechnung implements Dokument { // muss drucken(), speichern() UND getTitel() implementieren }
So baut man Schnittstellen-Hierarchien auf. Bei Klassen geht extends Klasse1, Klasse2 nicht – bei Interfaces schon.
9) Konstanten in Interfaces
Felder in Interfaces sind automatisch public static final – also Konstanten:
public interface MaxWerte { int MAX_LAENGE = 100; // implizit public static final String DEFAULT_NAME = "unbenannt"; } // Nutzung if (name.length() > MaxWerte.MAX_LAENGE) { ... }
Achtung: das ist heute eher schlechter Stil. Konstanten gehören in normale Klassen mit public static final – ein Interface sollte ein Verhaltensvertrag sein, kein Konstanten-Behälter.
10) Functional Interfaces
Ein Interface mit genau einer abstrakten Methode heißt Functional Interface. Das ist die Grundlage für Lambda-Ausdrücke (L7):
@FunctionalInterface public interface Rabattregel { double berechne(double betrag); } // Klassische Implementierung als Klasse: class FixerRabatt implements Rabattregel { @Override public double berechne(double b) { return b * 0.9; } } // Moderne Implementierung als Lambda: Rabattregel r = b -> b * 0.9; double netto = r.berechne(100); // 90
Lambda-Ausdrücke sind extrem kompakter Code für einfache Funktionen. Die Annotation @FunctionalInterface ist optional, schützt aber davor, dass jemand später versehentlich eine zweite abstrakte Methode hinzufügt.
11) Das Diamond-Problem und Java-Lösung
In Sprachen mit echter Klassen-Mehrfachvererbung (z. B. C++) gibt es ein Problem: was, wenn zwei Oberklassen dieselbe Methode haben? Java verbietet Klassen-Mehrfachvererbung und vermeidet das.
Bei Interfaces kann das aber durch Default-Methoden trotzdem auftauchen:
interface A { default void x() { System.out.println("A"); } } interface B { default void x() { System.out.println("B"); } } class C implements A, B { // Compile-Fehler: welche x() gilt? // Lösung: x() in C überschreiben und explizit wählen @Override public void x() { A.super.x(); // nutzt Default aus A } }
Java verlangt also, dass die Klasse den Konflikt aktiv löst. Das ist in der Praxis selten ein Problem – aber gut zu wissen.
12) Wann nehme ich Vererbung, wann Interface?
| Situation | Empfehlung |
|---|---|
| „X ist ein Y" mit gemeinsamem Zustand | Vererbung (extends) |
| „X kann Y machen" – Fähigkeit, kein Familienzweig | Interface |
| Klassen aus verschiedenen Hierarchien sollen dasselbe können | Interface |
| Mehrere Verträge in einer Klasse erfüllen | Mehrere Interfaces |
| Code-Wiederverwendung mit gemeinsamen Feldern | Abstrakte Klasse |
| API für Framework-Nutzer definieren | Interface (flexibler) |
| Template-Method-Muster (Skeleton-Algorithmus) | Abstrakte Klasse |
13) Beispiele aus der Java-Standardbibliothek
Interfaces, denen du als Java-Entwickler ständig begegnest:
Comparable<T>– Objekte vergleichbar machen (compareTo) → fürCollections.sort()Comparator<T>– externer VergleicherIterable<T>– durchlaufbar infor-eachRunnable/Callable<T>– Aufgaben für ThreadsList<T>,Set<T>,Map<K,V>– die Collections-InterfacesAutoCloseable– nutzbar intry-with-resourcesSerializable– kann in Byte-Strom umgewandelt werden
Abstrakte Klassen sind seltener, aber prominent: AbstractList als Basis für eigene Listen-Implementierungen, HttpServlet als Basis für Web-Servlets.
14) Klassen vs. Interfaces in Frameworks
Moderne Frameworks setzen fast ausschließlich auf Interfaces als API-Vertrag. Beispiel Spring: UserRepository extends JpaRepository – ein Interface, kein Code. Spring generiert die Implementierung zur Laufzeit. Das geht nur, weil Interfaces ein reiner Vertrag sind.
Du wirst in der Praxis viel mehr Interfaces schreiben als abstrakte Klassen. Programm gegen Interfaces, nicht gegen Implementierungen ist eines der wichtigsten OOP-Prinzipien (mehr dazu in K48 und K49).
15) Häufige Fehler
- Versuch, Interface zu instanziieren:
new Druckbar()geht nicht – nur konkrete Klassen können instanziiert werden - Vergessen, alle Methoden zu implementieren: Klasse bleibt selbst abstrakt, kann nicht instanziiert werden. Compiler-Hinweis genau lesen
- public bei Interface-Methoden vergessen: Interface-Methoden sind implizit
public. Die Implementierung in der Klasse muss auchpublicsein – sonst Compile-Fehler - Konstanten in Interface gepackt: schlechter Stil, gehört in eine eigene Konstanten-Klasse
- Default-Methode missbraucht für komplexe Logik: Default-Methoden sind als Bequemlichkeit gedacht, nicht als Hauptcode. Wenn ein Interface viel Logik enthält, ist eine abstrakte Klasse passender
- Statisches Interface-Mitglied bei Implementierung erwartet: statische Methoden eines Interface werden nicht mit-vererbt – Aufruf über den Interface-Namen
implements-Implementierung (Klasse wird selbst abstrakt).
Zusammenfassung
Ein Interface definiert einen Vertrag ohne Implementierung – mit implements verspricht eine Klasse, alle Methoden anzubieten. Eine abstrakte Klasse kann konkreten Code und abstrakte Methoden mischen, hat Felder und Konstruktoren, kann aber nicht direkt instanziiert werden. Eine Klasse kann mehrere Interfaces implementieren, aber nur von einer Klasse erben – so umgeht Java die Mehrfachvererbung. Seit Java 8 dürfen Interfaces auch default-Methoden mit Implementierung haben. Functional Interfaces (mit genau einer abstrakten Methode) sind die Basis für Lambda-Ausdrücke. Faustregel: Interface für Verhalten, abstrakte Klasse für Vererbungshierarchien mit gemeinsamem Zustand; im Zweifel Interface.
