Interfaces & abstrakte Klassen
Interfaces und abstrakte Klassen in Java: Verträge definieren, Default-Methoden, Sealed Interfaces und der Unterschied zwischen beiden Konzepten.
Interfaces & abstrakte Klassen
Du weisst bereits, dass Java nur Einfachvererbung unterstuetzt. Aber was, wenn eine Klasse mehrere “Faehigkeiten” haben soll? Hier kommen Interfaces ins Spiel — sie definieren einen Vertrag, den Klassen erfuellen muessen.
Was ist ein Interface?
Ein Interface ist wie ein Vertrag: Es definiert, welche Methoden eine Klasse haben muss, aber nicht wie sie implementiert werden:
public interface Schwimmbar {
void schwimmen();
}
public interface Fliegbar {
void fliegen();
}
// Eine Klasse kann MEHRERE Interfaces implementieren!
public class Ente implements Schwimmbar, Fliegbar {
@Override
public void schwimmen() {
System.out.println("Die Ente schwimmt.");
}
@Override
public void fliegen() {
System.out.println("Die Ente fliegt.");
}
}
var ente = new Ente();
ente.schwimmen(); // Die Ente schwimmt.
ente.fliegen(); // Die Ente fliegt.
Interface-Regeln
| Regel | Erlaeuterung |
|---|---|
implements statt extends | class Hund implements Haustier { } |
| Mehrere Interfaces moeglich | class Ente implements Schwimmbar, Fliegbar { } |
| Alle Methoden muessen implementiert werden | Sonst Kompilierfehler |
Methoden sind automatisch public abstract | Du musst public hinschreiben |
Felder sind automatisch public static final | Also Konstanten |
Interface definieren
public interface Haustier {
// Abstrakte Methode (muss implementiert werden)
String getName();
void fuettern();
void spielen();
// Konstante
int MAX_ALTER = 30; // public static final (automatisch)
}
public class Hund implements Haustier {
private String name;
Hund(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void fuettern() {
System.out.println(name + " frisst Hundefutter.");
}
@Override
public void spielen() {
System.out.println(name + " spielt mit dem Ball.");
}
}
Default-Methoden (Java 8+)
Interfaces koennen auch fertige Methoden enthalten:
public interface Begruesbar {
String getName();
// Default-Methode: hat eine Implementierung
default String begruessen() {
return "Hallo, ich bin " + getName() + "!";
}
default String verabschieden() {
return "Tschuess von " + getName() + "!";
}
}
public class Student implements Begruesbar {
private String name;
Student(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
// begruessen() und verabschieden() sind schon implementiert!
// Koennen aber ueberschrieben werden:
@Override
public String begruessen() {
return "Hey! Ich bin " + name + ", Student.";
}
}
var student = new Student("Max");
System.out.println(student.begruessen()); // Hey! Ich bin Max, Student.
System.out.println(student.verabschieden()); // Tschuess von Max! (Default)
Statische Methoden in Interfaces
public interface MathUtils {
static int max(int a, int b) {
return (a > b) ? a : b;
}
static int min(int a, int b) {
return (a < b) ? a : b;
}
static boolean istGerade(int zahl) {
return zahl % 2 == 0;
}
}
// Aufruf direkt ueber das Interface
var maximum = MathUtils.max(10, 20); // 20
var gerade = MathUtils.istGerade(4); // true
Abstrakte Klassen
Eine abstrakte Klasse ist ein Mittelding zwischen Klasse und Interface. Sie kann sowohl abstrakte (unimplementierte) als auch konkrete (implementierte) Methoden haben:
public abstract class Fahrzeug {
String name;
int geschwindigkeit;
Fahrzeug(String name) {
this.name = name;
this.geschwindigkeit = 0;
}
// Abstrakte Methode: MUSS von Unterklassen implementiert werden
abstract void beschleunigen();
// Konkrete Methode: Kann von Unterklassen geerbt werden
void bremsen() {
geschwindigkeit = Math.max(0, geschwindigkeit - 10);
System.out.printf("%s bremst. Geschwindigkeit: %d km/h%n", name, geschwindigkeit);
}
void status() {
System.out.printf("%s faehrt %d km/h%n", name, geschwindigkeit);
}
}
public class Auto extends Fahrzeug {
Auto(String name) {
super(name);
}
@Override
void beschleunigen() {
geschwindigkeit += 20;
System.out.printf("%s beschleunigt. Geschwindigkeit: %d km/h%n", name, geschwindigkeit);
}
}
public class Fahrrad extends Fahrzeug {
Fahrrad(String name) {
super(name);
}
@Override
void beschleunigen() {
geschwindigkeit += 5;
System.out.printf("%s tritt in die Pedale. Geschwindigkeit: %d km/h%n", name, geschwindigkeit);
}
}
// var f = new Fahrzeug("Test"); // FEHLER! Abstrakte Klassen kann man nicht instanziieren
var auto = new Auto("BMW");
auto.beschleunigen(); // BMW beschleunigt. Geschwindigkeit: 20 km/h
auto.beschleunigen(); // BMW beschleunigt. Geschwindigkeit: 40 km/h
auto.bremsen(); // BMW bremst. Geschwindigkeit: 30 km/h
Interface vs. abstrakte Klasse
| Aspekt | Interface | Abstrakte Klasse |
|---|---|---|
| Schluesselwort | interface | abstract class |
| Implementierung | implements | extends |
| Mehrfach moeglich | Ja | Nein (nur 1 Oberklasse) |
| Konstruktor | Nein | Ja |
| Felder | Nur Konstanten | Beliebige Felder |
| Methoden | Abstract + Default | Abstract + Konkret |
| Instanziierbar | Nein | Nein |
Wann was verwenden?
// Interface: Fuer Faehigkeiten ("kann")
interface Serializable { }
interface Comparable<T> { }
interface Druckbar { void drucken(); }
// Abstrakte Klasse: Fuer gemeinsame Basis ("ist-ein")
abstract class Tier { String name; abstract void laut(); }
abstract class Form { abstract double flaeche(); }
Faustregel:
- Interface: Wenn verschiedene, nicht verwandte Klassen dasselbe Verhalten teilen
- Abstrakte Klasse: Wenn Klassen eine gemeinsame Basis mit geteiltem Zustand haben
Sealed Interfaces (Java 17+)
public sealed interface Zahlungsmittel permits Bargeld, Kreditkarte, PayPal {
double betrag();
String bezeichnung();
}
record Bargeld(double betrag) implements Zahlungsmittel {
public String bezeichnung() { return "Bargeld"; }
}
record Kreditkarte(double betrag, String kartenNr) implements Zahlungsmittel {
public String bezeichnung() { return "Kreditkarte " + kartenNr; }
}
record PayPal(double betrag, String email) implements Zahlungsmittel {
public String bezeichnung() { return "PayPal " + email; }
}
// Pattern Matching mit sealed Interface
Zahlungsmittel zahlung = new Kreditkarte(49.99, "****1234");
var info = switch (zahlung) {
case Bargeld b -> "Bar: %.2f EUR".formatted(b.betrag());
case Kreditkarte k -> "%s: %.2f EUR".formatted(k.bezeichnung(), k.betrag());
case PayPal p -> "%s: %.2f EUR".formatted(p.bezeichnung(), p.betrag());
};
// Kein default noetig -- sealed!
Funktionale Interfaces (Vorschau)
Ein Interface mit genau einer abstrakten Methode ist ein funktionales Interface. Es kann mit einem Lambda-Ausdruck implementiert werden:
@FunctionalInterface
public interface Berechnung {
double ausfuehren(double a, double b);
}
// Mit Lambda
Berechnung addition = (a, b) -> a + b;
Berechnung multiplikation = (a, b) -> a * b;
System.out.println(addition.ausfuehren(5, 3)); // 8.0
System.out.println(multiplikation.ausfuehren(5, 3)); // 15.0
Vergleich mit Python
# Python: "Duck Typing" -- kein Interface noetig
class Ente:
def schwimmen(self):
print("Schwimmt")
class Schiff:
def schwimmen(self):
print("Schwimmt auch")
# Beide haben schwimmen() -- reicht!
// Java: Interfaces machen den Vertrag explizit
interface Schwimmbar {
void schwimmen();
}
class Ente implements Schwimmbar {
public void schwimmen() { System.out.println("Schwimmt"); }
}
class Schiff implements Schwimmbar {
public void schwimmen() { System.out.println("Schwimmt auch"); }
}
Uebungen
Uebung 1: Druckbar-Interface
Erstelle ein Interface Druckbar mit der Methode drucken(). Implementiere es fuer Dokument, Bild und Tabelle.
Uebung 2: Vergleichbar
Erstelle ein Interface Vergleichbar mit vergleicheMit(Object o). Implementiere es fuer eine Klasse Student (Vergleich nach Note).
Uebung 3: Formen-System
Erstelle ein Interface Form mit flaeche() und umfang(). Erstelle eine abstrakte Klasse ZweiDForm und konkrete Klassen Kreis, Rechteck.
Uebung 4: Plugin-System
Erstelle ein Interface Plugin mit getName(), getVersion() und ausfuehren(). Erstelle zwei Plugins und rufe sie in einer Schleife auf.
Was kommt als Naechstes?
In der naechsten Lektion lernst du Polymorphismus — die Faehigkeit, ein Objekt als seinen Obertyp zu behandeln und trotzdem das richtige Verhalten zu bekommen.
Zusammenfassung
- Interfaces definieren Vertraege mit
interfaceundimplements - Eine Klasse kann mehrere Interfaces implementieren
- Default-Methoden ermoglichen fertige Implementierungen in Interfaces
- Abstrakte Klassen kombinieren abstrakte und konkrete Methoden
- Abstrakte Klassen koennen Felder und Konstruktoren haben
- Sealed Interfaces begrenzen die erlaubten Implementierungen
- Funktionale Interfaces haben genau eine abstrakte Methode (fuer Lambdas)
Pro-Tipp: In modernem Java sind Interfaces fast immer die bessere Wahl gegenueber abstrakten Klassen. Mit Default-Methoden, statischen Methoden und sealed Interfaces kannst du fast alles abdecken. Verwende abstrakte Klassen nur, wenn du wirklich gemeinsamen Zustand (Felder) und Konstruktoren brauchst. In IntelliJ kannst du mit Ctrl + I schnell alle Methoden eines Interfaces implementieren lassen!