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.
@Overridedarü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.privateAttribute der Oberklasse sieht die Unterklasse nicht direkt. Sie benutzt die Getter, wiegetNights().
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
thisvorotherkommt, - 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 Compilerconstructor 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@Overridemeldet 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 mitVehicle is abstract; cannot be instantiated. Lege ein Objekt einer Unterklasse an und speichere es ruhig in einer Variablen vom TypVehicle.