Informatik

Abstrakte Klassen

Die Klasse Form aus der letzten Lektion hatte eine Schwachstelle:

public double flaeche() {
    return 0.0;
}

Das ist keine Antwort, sondern eine Notlüge. Eine „allgemeine Form" hat schlicht keinen Flächeninhalt – und wer eine neue Unterklasse schreibt und das Überschreiben vergisst, bekommt keinen Fehler, sondern lautlos falsche Zahlen.

Zwei neue Schlüsselwörter

Eine abstrakte Klasse kann nicht instanziiert werden – von ihr lässt sich kein Objekt erzeugen. Sie dient nur als gemeinsame Oberklasse.

Eine abstrakte Methode hat keinen Rumpf, sondern endet nach dem Kopf mit einem Semikolon. Sie legt fest, dass es die Methode geben muss, und verpflichtet jede nicht-abstrakte Unterklasse, sie zu überschreiben.

Aufgabe: Zwei Fehlermeldungen lesen

Probier beides aus und lies jedes Mal die Fehlermeldung:

a) Entferne die Schrägstriche vor Form f = new Form("irgendwas");.

b) Mach die Zeile wieder zum Kommentar und kommentiere stattdessen in Rechteck die Methode flaeche() aus.

c) Beurteile: Was ist an beiden Meldungen besser als am Verhalten der Fassung aus der letzten Lektion?

Lösung. Erfrage das Passwort bei deiner Lehrkraft.
abstrakte Klasse normale Klasse
Objekte erzeugbar? nein ja
darf abstrakte Methoden haben? ja nein
darf normale Methoden mit Rumpf haben? ja ja
darf Attribute haben? ja ja
darf einen Konstruktor haben? ja ja
darf als Typ einer Variablen dienen? ja ja

Der Konstruktor bleibt sinnvoll, obwohl man keine Objekte erzeugen kann: Die Unterklassen rufen ihn mit super(...) auf, und er setzt die geerbten Attribute.

Der Typ bleibt sinnvoll, obwohl es keine Objekte gibt: Form[] formen und Form f = new Kreis(2.0) sind erlaubt. Nur das new Form(...) ist es nicht.

Im Diagramm wird der Name einer abstrakten Klasse und einer abstrakten Methode kursiv geschrieben. In der Schreibweise dieses Buches steht <<abstract>> über dem Klassennamen und ein Sternchen hinter der Methode.

classDiagram
    class Form {
        <<abstract>>
        #bezeichnung: String
        +Form(pBezeichnung: String)
        +getBezeichnung() String
        +flaeche() double*
        +groesserAls(pAndere: Form) boolean
    }
    class Kreis {
        -radius: double
        +Kreis(pRadius: double)
        +flaeche() double
    }
    Form <|-- Kreis

Wann abstrakt, wann konkret?

Die Frage, die entscheidet, lautet: Kann es davon ein sinnvolles einzelnes Objekt geben?

  • „Ein Fahrzeug, das kein Auto, Fahrrad oder LKW ist" – gibt es nicht. Fahrzeug wird abstrakt.
  • „Ein Rechteck, das kein Quadrat ist" – gibt es massenhaft. Rechteck bleibt konkret.

Eine zweite Frage hilft, wenn die erste nicht weiterbringt: Kann die Oberklasse für jede ihrer Methoden einen sinnvollen Rumpf angeben? Wo sie das nicht kann, wird die Methode abstrakt – und damit die Klasse.


Teil 1: Lesen

Aufgabe 1: Was meldet der Übersetzer?

Ohne Rechner. Gegeben ist die abstrakte Klasse Sensor. Entscheide für jeden der sechs Ausschnitte: übersetzt er? Wenn nein: warum nicht, und wie berichtigst du ihn?

public abstract class Sensor {
 
    private String ort;
 
    public Sensor(String pOrt) {
        ort = pOrt;
    }
 
    public String getOrt() {
        return ort;
    }
 
    public abstract int messwert();
 
    public String bericht() {
        return ort + ": " + messwert();
    }
}
// 1
public class Thermometer extends Sensor {
    public Thermometer(String pOrt) {
        super(pOrt);
    }
    public int messwert() {
        return 21;
    }
}
 
// 2
Sensor s = new Thermometer("Flur");
 
// 3
Sensor s = new Sensor("Flur");
 
// 4
public class Hygrometer extends Sensor {
    public Hygrometer(String pOrt) {
        super(pOrt);
    }
}
 
// 5
Sensor[] anlage = new Sensor[3];
 
// 6
public abstract int messwert() {
    return 0;
}
Tipp: zwei Verbote, sonst nichts

Eine abstrakte Klasse verbietet genau zwei Dinge:

  1. new auf ihr selbst.
  2. Eine nicht-abstrakte Unterklasse, die eine abstrakte Methode offen lässt.

Alles andere – Attribute, Konstruktoren, normale Methoden, Variablen und Felder von diesem Typ – ist erlaubt.

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Aufgabe 2: Abstrakt oder konkret?

Ohne Rechner. Entscheide für jede Oberklasse, ob sie abstrakt sein sollte. Begründe jedes Mal mit der Frage „Kann es davon ein sinnvolles einzelnes Objekt geben?"

a) Fahrzeug mit den Unterklassen Auto, Fahrrad, LKW

b) Rechteck mit der Unterklasse Quadrat

c) Mitarbeiter mit den Unterklassen Angestellter, Honorarkraft

d) Konto mit den Unterklassen Girokonto, Sparkonto

e) Medium mit den Unterklassen Buch, Dvd, Zeitschrift – aus 1.3

f) Spielfigur mit den Unterklassen Spieler, Gegner

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Teil 2: Schreiben

Aufgabe 3: Die Firma

Eine Firma beschäftigt Angestellte (festes Monatsgehalt nach Gehaltsstufe: Stufe mal 1000 Euro) und Honorarkräfte (Stundenlohn mal geleistete Stunden).

Setze die Hierarchie so um, dass alle Tests grün werden. Beachte, dass Firma keine der beiden Beschäftigungsarten kennen darf.

classDiagram
    class Mitarbeiter {
        <<abstract>>
        #name: String
        +Mitarbeiter(pName: String)
        +getName() String
        +berechneGehalt() double*
        +jahresgehalt() double
    }
    class Angestellter {
        -stufe: int
        +Angestellter(pName: String, pStufe: int)
        +berechneGehalt() double
    }
    class Honorarkraft {
        -stunden: double
        -stundensatz: double
        +Honorarkraft(pName: String, pStunden: double, pSatz: double)
        +berechneGehalt() double
    }
    class Firma {
        -team: Mitarbeiter[]
        -anzahl: int
        +Firma(pMaxGroesse: int)
        +stelleEin(pPerson: Mitarbeiter) boolean
        +gehaltssumme() double
        +bestbezahlt() String
    }
    Mitarbeiter <|-- Angestellter
    Mitarbeiter <|-- Honorarkraft
    Firma "1" --> "0..*" Mitarbeiter : beschäftigt
Tipp 1: Warum kann das Feld beide Sorten aufnehmen?

Weil beide Mitarbeiter sind. Ein Mitarbeiter[] nimmt jedes Objekt auf, dessen Klasse von Mitarbeiter erbt – auch wenn Mitarbeiter abstrakt ist.

Und beim Aufruf team[i].berechneGehalt() greift die dynamische Bindung: Bei einer Angestellten rechnet Java mit der Gehaltsstufe, bei einer Honorarkraft mit den Stunden. Die Klasse Firma muss die beiden Sorten überhaupt nicht kennen.

Tipp 2: jahresgehalt

Eine Zeile, und sie steht nur in Mitarbeiter:

return 12 * berechneGehalt();

Dass berechneGehalt() dort abstrakt ist, stört nicht – zur Laufzeit gibt es immer ein konkretes Objekt, und dessen Fassung wird genommen.

Tipp 3: bestbezahlt

Das Muster aus der Einführungsphase: Merk dir den Index der bisher bestbezahlten Person, nicht den Betrag – sonst kommst du am Ende nicht an den Namen.

Und denk an den Sonderfall anzahl == 0; der Test prüft ihn.

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Aufgabe 4: Die Eisdiele

Eine Eisdiele verkauft zwei Sorten von Bechern:

  • Standardbecher zu festen Preisen: klein 6 €, mittel 10 €, groß 15 €.
  • Wunschbecher, bei denen man die Kugeln selbst wählt: 1,20 € pro Kugel.

Diesmal gibt es kein Gerüst. Entwirf selbst:

a) Modelliere die Hierarchie. Welche Klasse ist abstrakt, welche konkret? Was steht in der Oberklasse, was in den Unterklassen?

b) Zeichne das Implementationsdiagramm.

c) Setze es im Programmierbereich um und schreib eine Testklasse mit mindestens sechs Testfällen, darunter mindestens zwei Sonderfälle.

Tipp 1: Was gehört in die Oberklasse?

Alles, was für jeden Becher gilt: eine Bezeichnung vielleicht, der Name der Eissorte – und die abstrakte Methode preis().

Die Frage, aus der man die Antwort ableitet, ist immer dieselbe: Was ist bei allen gleich, was ist bei jedem anders? Was anders ist und keinen sinnvollen Standardwert hat, wird abstrakt.

Tipp 2: Sonderfälle für die Tests

Zwei liegen auf der Hand: ein Wunschbecher mit null Kugeln, und ein Standardbecher mit einer Größe, die es nicht gibt.

Rechne in Cent statt in Euro. Dann arbeitest du mit int und brauchst dich nicht um Rundungsfehler zu kümmern – 1,20 € sind 120 Cent.

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Zum Weiterdenken

Vertiefung 1: Die Oberklasse gibt den Ablauf vor

jahresgehalt() in Aufgabe 3 war ein kleines Beispiel für ein großes Muster: Die Oberklasse schreibt den Ablauf auf und lässt die Einzelschritte offen.

a) Sag voraus, was das Programm ausgibt.

b) Die Klasse Auswertung enthält den vollständigen Ablauf, ohne eine einzige Unterklasse zu kennen. Welche Schritte legt sie fest, welche nicht?

c) Was passierte, wenn fuehreAus() nicht in der Oberklasse stünde, sondern jede Unterklasse ihren Ablauf selbst schriebe? Nenne zwei Nachteile.

d) fuehreAus() ist bewusst nicht abstrakt und soll auch nicht überschrieben werden. Java kennt dafür das Schlüsselwort final vor einer Methode. Erkläre, warum das hier passt – und welchen Nachteil es hat.

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Vertiefung 2: Abstrakt heißt nicht leer

Vier Behauptungen über abstrakte Klassen. Entscheide für jede: stimmt sie? Begründe, und gib bei den falschen an, was stattdessen gilt.

a) „Eine abstrakte Klasse darf keinen Konstruktor haben – man kann ja kein Objekt erzeugen."

b) „Eine abstrakte Klasse muss mindestens eine abstrakte Methode haben."

c) „Eine abstrakte Methode darf private sein."

d) „Eine Klasse darf gleichzeitig abstract und final sein."

e) Zusatz: In 1.3 hast du gesehen, dass eine Vererbungskette selten mehr als drei Ebenen tragen sollte. Wo in dieser Kette stehen abstrakte Klassen typischerweise – und warum?

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Selbsttest

Abstrakte Klassen

Teilbare URL erstellen

Abschnitte auswählen