Zum Inhalt springen
Java Anfänger 25 min

Polymorphismus

Polymorphismus in Java verstehen: Laufzeit-Polymorphismus, Compile-Zeit-Polymorphismus, Upcasting, Downcasting und praktische Anwendungen.

Aktualisiert:

Polymorphismus in Java

Polymorphismus bedeutet woertlich “Vielgestaltigkeit”. In Java heisst das: Ein Objekt kann als sein eigener Typ oder als ein Typ seiner Oberklasse/seines Interfaces behandelt werden — und trotzdem das richtige Verhalten zeigen.

Was ist Polymorphismus?

Stell dir vor, du hast verschiedene Tiere und rufst bei jedem laut() auf:

public class Tier {
    String name;

    Tier(String name) {
        this.name = name;
    }

    String laut() {
        return "...";
    }
}

public class Hund extends Tier {
    Hund(String name) { super(name); }

    @Override
    String laut() { return "Wuff!"; }
}

public class Katze extends Tier {
    Katze(String name) { super(name); }

    @Override
    String laut() { return "Miau!"; }
}

public class Vogel extends Tier {
    Vogel(String name) { super(name); }

    @Override
    String laut() { return "Piep!"; }
}

Jetzt kommt die Magie:

// Alle Tiere in einer Liste (Typ: Tier)
Tier[] tiere = {
    new Hund("Rex"),
    new Katze("Mia"),
    new Vogel("Tweety"),
    new Hund("Bella")
};

// Polymorphismus: Java ruft die RICHTIGE Methode auf!
for (var tier : tiere) {
    System.out.printf("%s sagt: %s%n", tier.name, tier.laut());
}

Ausgabe:

Rex sagt: Wuff!
Mia sagt: Miau!
Tweety sagt: Piep!
Bella sagt: Wuff!

Obwohl die Variable den Typ Tier hat, ruft Java die ueberschriebene Methode der tatsaechlichen Klasse auf. Das ist Polymorphismus!

Arten von Polymorphismus

1. Laufzeit-Polymorphismus (Methoden ueberschreiben)

Die Methode wird zur Laufzeit basierend auf dem tatsaechlichen Objekttyp ausgewaehlt:

Tier tier = new Hund("Rex"); // Variable: Tier, Objekt: Hund
tier.laut(); // Ruft Hund.laut() auf --> "Wuff!"

2. Compile-Zeit-Polymorphismus (Methoden ueberladen)

Die Methode wird zur Kompilierzeit basierend auf den Argumenten ausgewaehlt:

static void ausgabe(int x)    { System.out.println("int: " + x); }
static void ausgabe(double x) { System.out.println("double: " + x); }
static void ausgabe(String x) { System.out.println("String: " + x); }

ausgabe(42);      // int: 42
ausgabe(3.14);    // double: 3.14
ausgabe("Hallo"); // String: Hallo

Upcasting und Downcasting

Upcasting (automatisch)

Ein Objekt einer Unterklasse kann als Oberklasse behandelt werden:

Hund rex = new Hund("Rex");
Tier tier = rex; // Upcasting: Hund -> Tier (automatisch)

tier.laut();     // "Wuff!" -- richtige Methode!
// tier.bellen();  // FEHLER! Tier hat kein bellen()

Downcasting (explizit)

Zurueckcasten braucht einen expliziten Cast:

Tier tier = new Hund("Rex");

// Pruefung mit instanceof
if (tier instanceof Hund hund) {
    hund.bellen(); // Jetzt koennen wir Hund-Methoden nutzen
}
// Pattern Matching (Java 16+)
Tier tier = new Katze("Mia");

if (tier instanceof Katze katze) {
    katze.miauen();
} else if (tier instanceof Hund hund) {
    hund.bellen();
}

Polymorphismus mit Interfaces

Interfaces sind der haeufigste Anwendungsfall fuer Polymorphismus:

interface Zeichenbar {
    void zeichnen();
}

class Kreis implements Zeichenbar {
    @Override
    public void zeichnen() {
        System.out.println("Zeichne einen Kreis");
    }
}

class Rechteck implements Zeichenbar {
    @Override
    public void zeichnen() {
        System.out.println("Zeichne ein Rechteck");
    }
}

class Dreieck implements Zeichenbar {
    @Override
    public void zeichnen() {
        System.out.println("Zeichne ein Dreieck");
    }
}
// Alle Formen in einer Liste
var formen = List.<Zeichenbar>of(
    new Kreis(),
    new Rechteck(),
    new Dreieck(),
    new Kreis()
);

// Polymorphismus: Jede Form zeichnet sich selbst
for (var form : formen) {
    form.zeichnen();
}

Praktische Beispiele

Zahlungssystem

interface Zahlungsmethode {
    boolean bezahlen(double betrag);
    String getName();
}

class Bargeld implements Zahlungsmethode {
    private double vorrat;

    Bargeld(double vorrat) { this.vorrat = vorrat; }

    @Override
    public boolean bezahlen(double betrag) {
        if (betrag <= vorrat) {
            vorrat -= betrag;
            System.out.printf("%.2f EUR bar bezahlt. Rest: %.2f EUR%n", betrag, vorrat);
            return true;
        }
        System.out.println("Nicht genug Bargeld!");
        return false;
    }

    @Override
    public String getName() { return "Bargeld"; }
}

class Kreditkarte implements Zahlungsmethode {
    private String nummer;
    private double limit;
    private double ausgegeben;

    Kreditkarte(String nummer, double limit) {
        this.nummer = nummer;
        this.limit = limit;
    }

    @Override
    public boolean bezahlen(double betrag) {
        if (ausgegeben + betrag <= limit) {
            ausgegeben += betrag;
            System.out.printf("%.2f EUR mit Karte %s bezahlt.%n", betrag, nummer);
            return true;
        }
        System.out.println("Kreditlimit erreicht!");
        return false;
    }

    @Override
    public String getName() { return "Kreditkarte " + nummer; }
}
// Polymorphismus: Gleicher Code fuer verschiedene Zahlungsmethoden
Zahlungsmethode methode = new Kreditkarte("****1234", 5000);
methode.bezahlen(49.99); // Funktioniert mit jeder Zahlungsmethode!

// Auch in Listen:
var methoden = List.<Zahlungsmethode>of(
    new Bargeld(100),
    new Kreditkarte("****5678", 2000)
);

for (var m : methoden) {
    System.out.println("Bezahle mit: " + m.getName());
    m.bezahlen(25.50);
}

Benachrichtigungssystem

interface Benachrichtigung {
    void senden(String empfaenger, String nachricht);
}

class EmailBenachrichtigung implements Benachrichtigung {
    @Override
    public void senden(String empfaenger, String nachricht) {
        System.out.printf("E-Mail an %s: %s%n", empfaenger, nachricht);
    }
}

class SMSBenachrichtigung implements Benachrichtigung {
    @Override
    public void senden(String empfaenger, String nachricht) {
        System.out.printf("SMS an %s: %s%n", empfaenger, nachricht);
    }
}

class PushBenachrichtigung implements Benachrichtigung {
    @Override
    public void senden(String empfaenger, String nachricht) {
        System.out.printf("Push an %s: %s%n", empfaenger, nachricht);
    }
}
// Die Methode arbeitet mit dem Interface -- egal welche Implementierung
static void benachrichtigeAlle(List<Benachrichtigung> kanaele,
                                String empfaenger, String nachricht) {
    for (var kanal : kanaele) {
        kanal.senden(empfaenger, nachricht);
    }
}

var kanaele = List.<Benachrichtigung>of(
    new EmailBenachrichtigung(),
    new SMSBenachrichtigung(),
    new PushBenachrichtigung()
);

benachrichtigeAlle(kanaele, "max@example.com", "Deine Bestellung ist da!");

Pattern Matching mit switch (Java 21+)

static String beschreibe(Object obj) {
    return switch (obj) {
        case Integer i when i > 0 -> "Positive Ganzzahl: " + i;
        case Integer i -> "Nicht-positive Ganzzahl: " + i;
        case String s when s.length() > 10 -> "Langer Text";
        case String s -> "Kurzer Text: " + s;
        case Double d -> "Dezimalzahl: " + d;
        case null -> "null-Wert";
        default -> "Unbekannter Typ: " + obj.getClass().getSimpleName();
    };
}

Uebungen

Uebung 1: Tier-Polymorphismus

Erstelle eine Hierarchie mit verschiedenen Tieren und einer Methode tierGeraeuscheAbspielen(List<Tier> tiere), die alle Tiere ihre Laute machen laesst.

Uebung 2: Formberechnung

Erstelle ein Interface Form mit flaeche() und umfang(). Implementiere es fuer Kreis, Rechteck und Dreieck. Schreibe eine Methode, die die Gesamtflaeche einer Liste von Formen berechnet.

Uebung 3: Sortierbares Interface

Erstelle ein Interface Sortierbar mit vergleicheMit(Sortierbar anderes). Implementiere es fuer Student (nach Note) und nutze Polymorphismus zum Sortieren.

Uebung 4: Plugin-Manager

Erstelle ein Interface Plugin und mehrere Implementierungen. Schreibe einen PluginManager, der alle Plugins laden und ausfuehren kann.

Was kommt als Naechstes?

In der naechsten Lektion lernst du Kapselung (Encapsulation) — wie du mit Getter und Setter den Zugriff auf Felder kontrollierst und Daten schuetzt.

Zusammenfassung

  • Polymorphismus bedeutet: Ein Objekt verhaelt sich je nach seinem tatsaechlichen Typ
  • Laufzeit-Polymorphismus: Ueberschriebene Methoden werden zur Laufzeit aufgeloest
  • Compile-Zeit-Polymorphismus: Ueberladene Methoden werden beim Kompilieren aufgeloest
  • Upcasting (Unterklasse -> Oberklasse) passiert automatisch
  • Downcasting (Oberklasse -> Unterklasse) braucht instanceof-Pruefung
  • Polymorphismus mit Interfaces ist der haeufigste und flexibelste Ansatz
  • Pattern Matching in switch macht Polymorphismus noch eleganter

Pro-Tipp: Polymorphismus ist der Schlussel zu flexiblem, erweiterbarem Code. Wenn du merkst, dass du lange if-else-Ketten oder switch-Statements schreibst, die basierend auf einem Typ unterschiedliches Verhalten ausfuehren, ist das oft ein Zeichen, dass du Polymorphismus verwenden solltest. Ersetze die Bedingungen durch eine gemeinsame Schnittstelle und verschiedene Implementierungen!

Zurück zum Java Kurs