jario_
Wróć do przeglądu

Rozdział 9

Vererbung und Interfaces

Eine Suite ist ein Hotelzimmer, nur teurer. Statt alles noch einmal zu schreiben, erbt sie vom Zimmer und ändert nur, was anders ist. Interfaces gehen einen Schritt weiter: Sie legen nur fest, was eine Klasse können muss, nicht wie. Beides erlaubt Code, der mit vielen verschiedenen Klassen gleich umgeht.

Vererbung mit extends

class Suite extends Room heißt: Eine Suite hat alle Attribute und Methoden eines Room. Room ist die Oberklasse, Suite die Unterklasse.

  • Der Konstruktor der Unterklasse ruft in der ersten Zeile mit super(...) den der Oberklasse auf.
  • Eine Methode der Oberklasse kannst du überschreiben: gleicher Name, gleiche Parameter, neuer Rumpf. @Override darüber lässt den Compiler prüfen, dass es die Methode oben wirklich gibt.
  • super.price() ruft die Fassung der Oberklasse auf. So ergänzt du sie, statt die Formel zu kopieren.
  • private Attribute der Oberklasse sieht die Unterklasse nicht direkt. Sie benutzt die Getter, wie getNights().

Eine Variable vom Typ Room darf auch eine Suite enthalten. Welche price() läuft, entscheidet das Objekt, nicht der Typ der Variablen. Deshalb rechnet describe() bei der Suite mit dem höheren Preis, obwohl describe() nur in Room steht. Das heißt Polymorphie.

class Room {
    private final int nights;

    public Room(int nights) {
        this.nights = nights;
    }

    public int getNights() {
        return nights;
    }

    public double price() {
        return 80.0 * nights;
    }

    public String describe() {
        return nights + " nights: " + price() + " €";
    }
}

class Suite extends Room {
    public Suite(int nights) {
        super(nights);
    }

    @Override
    public double price() {
        return super.price() + 50.0 * getNights();
    }
}

public class Main {
    public static void main(String[] args) {
        Room room = new Room(2);
        Room suite = new Suite(2);
        System.out.println(room.describe());
        System.out.println(suite.describe());
    }
}

Abstrakte Klassen und Interfaces

Eine abstrakte Klasse (abstract class Vehicle) ist nur als Oberklasse gedacht. Von ihr selbst kannst du kein Objekt anlegen. Eine abstrakte Methode hat keinen Rumpf, nur den Kopf mit Semikolon. Jede Unterklasse muss sie umsetzen, sonst kompiliert sie nicht.

Ein Interface enthält nur Methodenköpfe: einen Vertrag. Eine Klasse erfüllt ihn mit implements und schreibt alle Methoden aus. Erben kann eine Klasse nur von einer Oberklasse, Interfaces kann sie beliebig viele umsetzen: class Lamp implements Switchable, Dimmable.

Methoden und Listen, die nur den allgemeinen Typ kennen (List<Vehicle>, PaymentMethod method), funktionieren mit jeder Unterklasse. Ob ein Objekt zusätzlich ein Interface umsetzt, prüft instanceof: if (v instanceof Electric e) gibt dir das Objekt gleich als e.

import java.util.ArrayList;
import java.util.List;

abstract class Vehicle {
    private final String name;

    public Vehicle(String name) {
        this.name = name;
    }

    public abstract int wheels();

    public String describe() {
        return name + " has " + wheels() + " wheels";
    }
}

interface Electric {
    int range();
}

class Bicycle extends Vehicle {
    public Bicycle() {
        super("Bicycle");
    }

    @Override
    public int wheels() {
        return 2;
    }
}

class ElectricCar extends Vehicle implements Electric {
    public ElectricCar() {
        super("Electric car");
    }

    @Override
    public int wheels() {
        return 4;
    }

    @Override
    public int range() {
        return 400;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Vehicle> fleet = new ArrayList<>();
        fleet.add(new Bicycle());
        fleet.add(new ElectricCar());
        for (Vehicle v : fleet) {
            System.out.println(v.describe());
            if (v instanceof Electric e) {
                System.out.println("  range: " + e.range() + " km");
            }
        }
    }
}

Sortieren mit Comparable

Collections.sort(list) sortiert eine Liste, wenn die Elemente Comparable sind. Zahlen und Texte sind es schon, eigene Klassen setzen dafür Comparable<Runner> um. Die einzige Methode compareTo(other) gibt zurück:

  • eine negative Zahl, wenn this vor other kommt,
  • eine positive Zahl, wenn es danach kommt,
  • 0, wenn beide gleich sind.

Integer.compare(a, b) liefert genau das für zwei Zahlen. Für absteigende Reihenfolge vertauschst du die Argumente. Für einen Gleichstand vergleichst du danach ein zweites Merkmal, etwa den Namen mit name.compareTo(other.name).

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class Runner implements Comparable<Runner> {
    private final String name;
    private final int seconds;

    public Runner(String name, int seconds) {
        this.name = name;
        this.seconds = seconds;
    }

    @Override
    public int compareTo(Runner other) {
        return Integer.compare(seconds, other.seconds);
    }

    @Override
    public String toString() {
        return name + " (" + seconds + " s)";
    }
}

public class Main {
    public static void main(String[] args) {
        List<Runner> runners = new ArrayList<>();
        runners.add(new Runner("Mia", 52));
        runners.add(new Runner("Leo", 48));
        runners.add(new Runner("Ana", 55));
        Collections.sort(runners);
        System.out.println(runners);

        System.out.println("Ada".compareTo("Bob"));
        System.out.println("Bob".compareTo("Ada"));
        System.out.println("Ada".compareTo("Ada"));
    }
}

Typowe błędy

  • super(...) fehlt

    Hat die Oberklasse nur einen Konstruktor mit Parametern, muss jede Unterklasse ihn mit super(...) aufrufen, und zwar in der ersten Zeile. Sonst meldet der Compiler constructor Room in class Room cannot be applied to given types.

  • Überschreiben ohne @Override

    Ein Tippfehler wie public double prize() oder ein anderer Parametertyp legt still eine neue Methode an, die nie aufgerufen wird. Mit @Override meldet der Compiler den Fehler sofort.

  • Attribut in der Unterklasse noch einmal anlegen

    Legt die Unterklasse ein Attribut mit demselben Namen wie die Oberklasse an, hat das Objekt danach zwei davon. Die Methoden der Oberklasse sehen nur ihr eigenes:

    class Suite extends Room {
        private int nights; // hides Room's nights, stays 0
    }

    Attribute gehören nur in die Klasse, die sie einführt.

  • Objekt einer abstrakten Klasse

    new Vehicle("Bus") meldet der Compiler mit Vehicle is abstract; cannot be instantiated. Lege ein Objekt einer Unterklasse an und speichere es ruhig in einer Variablen vom Typ Vehicle.