Inhalt
9.2.1 Unterklassen sind Spezialisten
Wir gehen jetzt - ganz im Sinne der Spezialisierung - von der Klasse Buch aus und erstellen zwei Unterklassen Roman und Sachbuch. Alle Gemeinsamkeiten der beiden Unterklassen haben wir in der Klasse Buch belassen, die dadurch zu einer gemeinsamen Oberklasse "befördert" wird.
Wichtig ist dabei: Die Methode zeige() der Oberklasse Buch gibt bereits die allgemeinen Buchdaten aus. Die Unterklassen nutzen diese Implementierung über super.zeige() und ergänzen anschließend ihre spezifischen Informationen.
public class Roman extends Buch
{
String genre;
public Roman(String titel, String autor, int jahr, String genre)
{
super(titel, autor, jahr);
this.genre = genre;
}
@Override
public void zeige()
{
super.zeige();
System.out.println("Genre : " + genre);
}
public class Sachbuch extends Buch
{
String fachgebiet;
public Sachbuch(String titel, String autor, int jahr, String gebiet)
{
super(titel, autor, jahr);
this.fachgebiet = gebiet;
}
@Override
public void zeige()
{
super.zeige();
System.out.println("Fachgebiet : " + fachgebiet);
}
}
Quelltext:Sachbuch
Wir besprechen jetzt all diese Änderungen am Beispiel der Unterklasse Roman.
9.2.2 Vererbung am Beispiel der Unterklasse Roman
public class Roman extends Buch // auf Oberklasse bezogen
{
String genre; // Spezialisierung
public Roman(String titel, String autor, int jahr, String genre)
{
super(titel, autor, jahr); // auf Oberklasse bezogen
this.genre = genre; // Spezialisierung
}
@Override // auf Oberklasse bezogen
public void zeige()
{
super.zeige(); // auf Oberklasse bezogen
System.out.println("Genre : " + genre); // Spezialisierung
}
}
Die Klasse Roman ist eine Unterklasse von Buch. Das bedeutet:
- Ein Roman IST ein Buch (is-a-Beziehung, IST-Beziehung).
Objekte der Klasse Roman besitzen also alle Instanzvariablen und Methoden der Klasse Buch, die nicht als private deklariert sind. - Ein Roman hat zusätzliche Eigenschaften, die Objekte der Klasse Buch nicht haben. In unserem Beispiel ist das die Instanzvariable genre.
Das Schlüsselwort extends
Dieses Schlüsselwort bedeutet so viel wie "erbt von" oder - wörtlich übersetzt - "erweitert".
In der Tat erweitert die Klasse Roman die Klasse Buch um eine weitere Instanzvariable (genre) und eine erweiterte zeige()-Methode.
Das Schlüsselwort super
Im Konstruktor
Mit super(titel, autor, jahr) im Konstruktor der Klasse Roman wird der Konstruktor der Oberklasse Buch aufgerufen, dabei werden die Parameter titel, autor und jahr an diesen Oberklassen-Konstruktor weitergereicht.
Das ist notwendig, weil die Unterklasse nicht auf die privaten Attribute der Oberklasse zugreifen kann. Der Konstruktor von Buch kann jedoch von der Unterklasse Roman aufgerufen werden, er übernimmt dann die Initialisierung der privaten Instanzvariablen (direkt, wie in der einfachen Version von Buch, oder mit den Setter-Methoden wie in der erweiterten Buch-Klasse).
Im Konstruktor der Unterklasse Roman wird dann zusätzlich noch die Instanzvariable genre initialisiert.
Die Reihenfolge: Erst super(), dann weitere Initialisierungen, muss unbedingt eingehalten werden.
Der Aufruf von super() muss immer die erste Anweisung in einem Konstruktor der Unterklasse sein.
In der Methode zeige()
In der Methode zeige() von Roman wird mit super.zeige() die entsprechende Methode zeige() der Oberklasse Buch aufgerufen. Die Unterklasse Roman ergänzt anschließend die Ausgabe um ihre eigenen Informationen (genre).
Die Annotation @Override
Eine Annotation ist ein Hinweis an den Compiler, dass die folgende Methode eine Methode der Oberklasse überschreiben soll. Diese Annotation (Anmerkung) hat eine wichtige Aufgabe:
Wenn man sich in der Klasse Roman verschreibt, beispielsweise ziege() statt zeige(), dann würde der Compiler die falsch geschriebene Methode akzeptieren. Warum sollte die Klasse Roman nicht eine Methode ziege() haben, der Compiler weiß so etwas ja nicht.
Durch die Annotation @Override wird dem Compiler aber mitgeteilt, dass diese (falsch geschriebene) Methode ziege() eine gleichnamige Methode der Oberklasse überschreiben soll. Da es in der Oberklasse Buch aber keine Methode ziege() gibt, liegt hier offensichtlich ein Fehler vor, und der Compiler verweigert die Übersetzung.
Merke:
Unterklassen
Unterklassen erweitern eine Oberklasse um zusätzliche Eigenschaften und Verhalten.
Gemeinsame Funktionalität wird in der Oberklasse implementiert und in den Unterklassen wiederverwendet.
9.2.3 Polymorphie
Die Klasse Buchliste
Wir wollen nun die Klasse Buchliste so erweitern, dass nicht nur Objekte der Klasse Buch gespeichert werden können, sondern auch Objekte der beiden Unterklassen Roman und Sachbuch.
Da wir nun das Konzept der Vererbung einsetzen, wird die Erstellung einer solchen heterogenen Liste wesentlich vereinfacht, wie wir gleich sehen werden.
import java.util.ArrayList;
public class Buchliste
{
private ArrayList liste;
public Buchliste()
{
liste = new ArrayList<>();
erzeuge();
zeige();
}
public void erzeuge()
{
liste.add(new Sachbuch("Einführung in Java","Meier, Otto",2025,"Informatik"));
liste.add(new Sachbuch("Lexikon der Informatik","Duden-Verlag",2012,"Informatik"));
liste.add(new Buch("Landschaften Deutschlands","Tegeler, Jürgen",2020));
liste.add(new Roman("Der Herr der Ringe","J.R. Tolkien",1964, "Fantasy"));
liste.add(new Sachbuch("Biochemie der Pflanze","Nultsch, Karl",1987,"Biochemie"));
}
public void zeige()
{
for (Buch b: liste)
{
b.zeige();
}
}
}
Quelltext: Buchliste
In der Methode erzeuge() werden der Liste ein Buch-, ein Roman- und drei Sachbuch-Objekte zugefügt.
Die zeige()-Methode geht die Liste Element für Element durch und ruft dabei für jedes Objekt die passende zeige()-Methode auf.
Das erste Listenelement ist ein Objekt der Klasse Sachbuch (streng genommen: Eine Referenz auf ein Objekt der Klasse Sachbuch). Die Schleife in der Methode Buchliste.zeige() führt dann die Methode Sachbuch.zeige() aus.
Das dritte Listenelement ist ein Objekt der Klasse Buch, entsprechend wird beim Anzeigen Buch.zeige() ausgeführt.
Beim vierten Listenelement wird Roman.zeige() ausgeführt und beim fünften Element wieder Sachbuch.zeige().
Was bedeutet Polymorphie?
Wörtlich übersetzt bedeutet der Begriff "Polymorphie" so viel wie "Vielgestaltigkeit".
Auf unser Beispiel mit der Buchliste bezogen versteht man unter diesem Begriff Folgendes:
Eine Referenz der Oberklasse Buch kann zur Laufzeit auch auf Objekte der Unterklassen Roman und Sachbuch verweisen. Bei Methodenaufrufen wird dann automatisch die passende überschreibende Methode des tatsächlichen Objekts ausgeführt.
Das ArrayList-Objekt liste in der Klasse Buchliste ist folgendermaßen definiert:
private ArrayList<Buch> liste;
Das heißt, in liste dürfen nur Objekte gespeichert werden, die entweder vom Typ Buch sind oder einer Unterklasse von Buch angehören.
Somit handelt es sich bei dem Objekt liste zwar um eine heterogene Liste, da Objekte verschiedener Klassen in ihr gespeichert werden können. Allerdings dürfen nicht Objekte beliebiger Klassen in liste gespeichert werden, sondern nur Objekte der Klasse Buch und ihrer Unterklassen Roman und Sachbuch.
Sollten später einmal weitere Unterklassen von Buch erstellt werden, so können auch Objekte dieser Unterklassen in der Liste gespeichert werden.
Upcasting beim Einfügen in die Liste
liste.add(new Roman(...));
liste.add(new Sachbuch(...));
Die Methode add() erwartet ein Objekt der Klasse Buch. Das Einfügen eines Sachbuch-Objekts funktioniert trotzdem, weil ein Sachbuch ein Buch ist. In der Informatik-Didaktik spricht man auch gern von IST-Beziehungen: Ein Sachbuch IST ein Buch. Im Englischen hat sich dafür der Begriff is-a-relation eingebürgert.
Unter dem Begriff Upcasting versteht man ein Typecasting, also eine Typumwandlung, bei der ein "niedriger" Typ (Unterklasse) in einen "höheren" Typ (Oberklasse) umgewandelt wird. Das übergebene Sachbuch-Objekt wird beim Einfügen in die Liste in ein Buch-Objekt "hochgestuft" - daher Upcasting.
Polymorphie beim Durchlaufen der Liste
for (Buch b : liste)
{
b.zeige();
}
Das Objekt b ist eigentlich vom Typ Buch. Zur Laufzeit kann sie jedoch als Referenz-Variable auf ein Buch-Objekt, ein Roman-Objekt oder ein Sachbuch-Objekt verweisen.
Beim Aufruf b.zeige() entscheidet Java zur Laufzeit, welche zeige()-Methode wirklich gemeint ist.
Die Polymorphie ermöglicht es uns, eine einzige Schleife mit einem einzigen Methodenaufruf zu schreiben, die für jedes Objekt in der Liste die entsprechende zeige()-Methode aufruft, basierend auf dem Buch-Typ (Buch, Roman oder Fachbuch).
Ein Vorteil dieses Verfahrens: Wenn wir weitere Unterklassen von Buch anlegen, beispielsweise Fachbuch oder Lexikon, dann kann die obige for-each-Schleife unverändert übernommen werden.
9.2.4 Eine bessere Konsolenausgabe
Der IST-Zustand
So ganz perfekt ist die Konsolenausgabe der Klasse Buchliste noch nicht:
Die zeige()-Methoden der Unterklassen rufen zunächst die zeige()-Methode der Oberklasse Buch auf. Diese schreibt dann die Werte der drei Instanzvariablen titel, autor und jahr in die Konsole, eingerahmt von zwei gestrichelten Linien:
--------------------------------- Titel : Einführung in Java Autor : Meier, Otto Jahr : 2025 _________________________________/
Erst nachdem Buch.zeige() fertig ist, geben die Unterklassen ihr Genre (bei Roman) bzw. das Fachgebiet (bei Sachbuch) aus. Dadurch steht diese Zusatzinformation unterhalb der abschließenden Linie, was nicht gut aussieht.
Wie kann man diese Ausgabe besser gestalten?
Eine bessere Ausgabe
Die Klasse Buchliste müssen wir nicht verändern. Stattdessen erweitern wir die Klassen Buch, Roman und Sachbuch so, dass auch die Zusatzdaten innerhalb des Rahmens ausgegeben werden.
Die Idee: Die Oberklasse Buch erhält eine zusätzliche Methode zeigeDaten(). Diese Methode ist in der Oberklasse zunächst leer, wird aber von der Methode zeige() an der passenden Stelle aufgerufen. Die Unterklassen überschreiben anschließend zeigeDaten() und geben dort ihre speziellen Zusatzinformationen aus.
Überarbeiteter Code in der Klasse Buch:
public void zeige()
{
System.out.println("---------------------------------");
System.out.println("Titel : " + titel);
System.out.println("Autor : " + autor);
System.out.println("Jahr : " + jahr);
zeigeDaten();
System.out.println("---------------------------------");
System.out.println();
}
public void zeigeDaten()
{
}
Die neue Methode zeigeDaten() enthält in der Oberklasse Buch keinen Code, sondern dient als Platzhalter für die Unterklassen. Wichtig ist die Position des Methodenaufrufs innerhalb der Methode zeige(): Die Methode wird nach der Ausgabe der allgemeinen Daten, aber vor der unteren Trennlinie aufgerufen.
Die Unterklassen können zeigeDaten() nun überschreiben und dort ihre eigenen Zusatzinformationen ausgeben. Wird später die Methode zeige() aufgerufen, so werden zuerst die allgemeinen Daten des Buchs ausgegeben. Anschließend wird automatisch die passende zeigeDaten()-Methode der jeweiligen Unterklasse ausgeführt. Danach setzt die Methode zeige() der Oberklasse ihre Arbeit fort und gibt die untere Trennlinie aus. Dann sollte das Ganze wie gewünscht funktionieren.
Überarbeiteter Quelltext der Klasse Roman:
public class Roman extends Buch
{
private String genre;
public Roman(String titel, String autor, String genre, int jahr)
{
super(titel, autor, jahr);
this.genre = genre;
}
@Override
public void zeigeDaten()
{
System.out.println("Genre : " + genre);
}
}
Die Oberklassen-Methode zeige() wird nicht mehr überschrieben, stattdessen überschreibt die Klasse Roman die Buch-Methode zeigeDaten().
Was passiert jetzt zur Laufzeit?
Schauen wir uns die Ausgabemethode von Buch noch einmal an:
public void zeige()
{
System.out.println("---------------------------------");
System.out.println("Titel : " + titel);
System.out.println("Autor : " + autor);
System.out.println("Jahr : " + jahr);
zeigeDaten();
System.out.println("_________________________________/");
System.out.println();
}
Zunächst wird die obere Linie ausgegeben, dann werden die Werte der drei Instanzvariablen angezeigt. Beim Aufruf von zeigeDaten() greift jetzt das Polymorphie-Konzept: Wenn das aktuelle Objekt ein Roman ist, wird nicht die leere Methode Buch.zeigeDaten() ausgeführt, sondern die überschreibende Methode Roman.zeigeDaten():
@Override
public void zeigeDaten()
{
System.out.println("Genre : " + genre);
}
Dadurch erscheint das Genre an der richtigen Stelle im Rahmen.
Danach geht es in Buch.zeige() weiter, und die untere Linie wird ausgegeben.
So entsteht eine saubere, einheitliche Konsolenausgabe, ohne dass die Klasse Buchliste geändert werden muss:
--------------------------------- Titel : Einführung in Java Autor : Meier, Otto Jahr : 2025 Fachgebiet : Informatik _________________________________/ --------------------------------- Titel : Lexikon der Informatik Autor : Duden-Verlag Jahr : 2012 Fachgebiet : Informatik _________________________________/ --------------------------------- Titel : Landschaften Deutschlands Autor : Tegeler, Jürgen Jahr : 2020 _________________________________/ --------------------------------- Titel : Der Herr der Ringe Autor : J.R. Tolkien Jahr : 1964 Genre : Fantasy _________________________________/ --------------------------------- Titel : Biochemie der Pflanze Autor : Nultsch, Karl Jahr : 1987 Fachgebiet : Biochemie _________________________________/
9.2.5 Zwei weitere Unterklassen
Wir wollen die Klasse Sachbuch jetzt in weitere Unterklassen untergliedern, ganz nach dem Prinzip der Spezialisierung. Um die Sache übersichtlich zu halten, begnügen wir uns mit zwei Unterklassen von Sachbuch: Lehrbuch und Lexikon. Als zusätzliche Eigenschaft der Klasse Lehrbuch wählen wir das Anforderungsniveau: Anfänger, Fortgeschrittene, Experten. Die zusätzliche Eigenschaft der Klasse Lexikon soll die Zahl der Stichworte sein.
Die Klasse Lexikon
Beginnen wir mit der neuen Unterklasse Lexikon.
public class Lexikon extends Sachbuch
{
int artikelzahl;
public Lexikon(String titel, String autor, String gebiet, int artikelzahl, int jahr)
{
super(titel,autor,gebiet,jahr);
setArtikelzahl(artikelzahl);
}
public void setArtikelzahl(int zahl)
{
if (zahl < 1000)
throw new IllegalArgumentException("Zu geringe Anzahl an Artikeln: " + zahl);
if (zahl > 100000)
throw new IllegalArgumentException("Zu große Anzahl an Artikeln: " + zahl);
artikelzahl = zahl;
}
public int getArtikelzahl()
{
return artikelzahl;
}
public void zeigeSpezifischeDaten()
{
super.zeigeSpezifischeDaten();
System.out.println("Artikel : " + getArtikelzahl());
}
}
Wir erweitern dann die Methode erzeuge() der Klasse Buchliste um eine Zeile:
liste.add(new Lexikon("Römpp Lexikon der Chemie","Römpp et al.", 1989, "Chemie", 6500));
Die Konsolenausgabe sieht aus wie erwartet:
--------------------------------- Titel : Einführung in Java Autor : Meier, Otto Jahr : 2025 _________________________________/ --------------------------------- Titel : Lexikon der Informatik Autor : Duden-Verlag Jahr : 2012 Gebiet : Informatik _________________________________/ --------------------------------- Titel : Der Herr der Ringe Autor : J.R. Tolkien Jahr : 1964 Genre : Fantasy _________________________________/ --------------------------------- Titel : Prinzipien der OOP Autor : Müller, Jonathan Jahr : 2024 _________________________________/ --------------------------------- Titel : Römpps Chemielexikon Autor : Römpp, Justus Jahr : 1989 Gebiet : Chemie Artikel : 7500 _________________________________/
Die Methode zeigeDaten() der Unterklasse Lexikon überschreibt direkt zeigeDaten() der Klasse Sachbuch und indirekt auch zeigeDaten() der Oberklasse Buch. Sachbuch hat zeigeDaten() nämlich bereits von Buch geerbt und hat diese Methode selbst überschrieben.
Polymorphie: Wenn bei einem Lexikon-Objekt die Methode zeige() aus Buch ausgeführt wird, ruft diese intern zeigeDaten() auf. Wegen der Polymorphie wird dann die speziellste Version ausgeführt, also nicht Buch.zeigeDaten(), auch nicht Sachbuch.zeigeDaten(), sondern Lexikon.zeigeDaten().
Die Klasse Lehrbuch mit einer Enumeration
Bei der Klasse Lehrbuch führen wir als zusätzliches Attribut das Anforderungsniveau ein. Dabei lernen wir zugleich ein neues Java-Konstrukt kennen: die Enumeration beziehungsweise den sogenannten Aufzählungstyp.
Hier zunächst die Unterklasse Lehrbuch:
public class Lehrbuch extends Sachbuch
{
private Niveau niveau;
public Lehrbuch
(String titel, String autor, int jahr, String gebiet, Niveau niveau)
{
super(titel, autor, gebiet, jahr);
this.niveau = niveau;
}
@Override
public void zeigeSpezifischeDaten()
{
System.out.printf("%-12s: %-40s%n", "Fachgebiet", fachgebiet);
System.out.printf("%-12s: %-40s%n", "Niveau", niveau);
}
}
Das Anforderungsniveau wird hier nicht als String verwaltet, sondern als Aufzählungstyp bzw. Enumeration.
Enumerations
Schauen wir uns die Enumeration Niveau näher an:
public enum Niveau
{
ANFAENGER,
FORTGESCHRITTENE,
EXPERTEN
}
Eine Enumeration ist eine spezieller Klassentyp, der durch das Schlüsselwort enum eingeleitet wird. Ein solcher Aufzählungstyp benötigt meistens weder Konstruktor noch Methoden, sondern nur die Werte, die möglich sein sollen. In der Regel schreibt man diese Werte - ähnlich wie wie Java-Konstanten - in GROSSBUCHSTABEN.
Eine Enumeration verhält sich in gewisser Weise wie eine Klasse mit festen Konstanten. Man kann daher auf die Werte zugreifen, ohne zuvor ein Objekt der Enumeration erzeugen zu müssen. Betrachten wir dazu die entsprechende Stelle der Klasse Buchliste:
liste.add(new Buch("Einführung in Java","Meier, Otto",2025));
liste.add(new Sachbuch("Lexikon der Informatik","Duden-Verlag","Informatik",2012));
liste.add(new Roman("Der Herr der Ringe","J.R. Tolkien","Fantasy", 1964));
liste.add(new Buch("Prinzipien der OOP","Müller, Jonathan",2024));
liste.add(new Lexikon("Römpps Chemielexikon","Römpp, Justus","Chemie",7500,1989));
liste.add(new Lehrbuch("Java lernen","Müller, Anna", 2024,"Informatik",Niveau.ANFAENGER));
Wir können hier einfach Niveau.ANFAENGER schreiben, ähnlich wie bei einer Konstanten (zum Beispiel Math.PI oder Color.BLACK).
Vorteile von Aufzählungstypen
Enumerationen eignen sich immer dann, wenn ein Attribut nur eine kleine, fest vorgegebene Menge von Werten annehmen darf. Statt beliebige Strings zu verwenden, können wir dadurch einen eigenen Datentyp mit genau den erlaubten Werten definieren. Bei der Klasse Lehrbuch verhindern wir so, dass Objekte mit einem unsinnigen Anforderungsniveau wie beispielsweise "Dummkopf" erzeugt werden können.
Fassen wir die Vorteile, die Enumerationen bieten, einmal kurz und übersichtlich zusammen:
- Falsche oder unsinnige Werte werden bereits beim Kompilieren verhindert.
- Der Quelltext wird besser lesbar und verständlicher.
- Die Entwicklungsumgebung kann automatisch gültige Werte vorschlagen.
- Tippfehler werden vermieden.
- Änderungen an den erlaubten Werten lassen sich zentral an einer Stelle durchführen.
Hätten wir in der Klasse Lehrbuch statt der Enumeration Niveau einfach eine Instanzvariable niveau von Typ String gewählt, wären auch fehlerhafte Eingaben wie "Anfänger", "anfaenger" oder unsinnige Angaben wie "anhänger" oder "dummy" möglich.
Enumerations als innere Klassen
Man hätte die Aufzählung des Niveaus auch als innere Klasse von Lehrbuch implementieren können:
public class Lehrbuch extends Sachbuch
{
public enum Niveau
{
ANFAENGER,
FORTGESCHRITTENE,
EXPERTEN
}
private Niveau niveau;
public Lehrbuch(String titel, String autor, int jahr, String gebiet, Niveau niveau)
{
super(titel, autor, jahr, gebiet);
this.niveau = niveau;
}
@Override
public void zeigeDaten()
{
System.out.printf("%-12s: %-40s%n", "Fachgebiet", fachgebiet);
System.out.printf("%-12s: %-40s%n", "Niveau", niveau);
}
}
Allerdings wäre dann der Zugriff auf die Werte von außen etwas komplizierter, wie das folgende Beispiel zeigt:
Lehrbuch lb = new Lehrbuch
("Java lernen","Müller, Anna",2024,"Informatik",Lehrbuch.Niveau.ANFAENGER);
Man gibt also zunächst die äußere Klasse Lehrbuch an, welche die innere Klasse enthält, dann die innere Klasse Niveau, und schließlich den Wert ANFAENGER. Möglich ist das, weil die innere Klasse als public definiert wurde, nur so ist ein Zugriff von Klassen außerhalb von Lehrbuch möglich.
9.2.6 Klassenhierarchie und Klassendiagramm
In der Entwicklungsumgebung BlueJ wird die Klassenhierarchie, die wir bisher erzeugt haben, sehr übersichtlich dargestellt:
BlueJ-Klassendiagramm zum Buchprojekt
Autor: Ulrich Helmich 05/2026, Lizenz: Public domain
Die Vererbungs-Beziehungen werden hier durch spezielle Pfeile dargestellt, die von der Unterklasse auf die jeweilige Oberklasse verweisen.
Einen bessern Überblick bietet folgendes UML-Klassendiagramm:
UML-Klassendiagramm zum Buchprojekt
Autor: Ulrich Helmich 05/2026, Lizenz: Public domain
Allgemeines zu UML-Klassendiagrammen (KI-Text)
Der Text in diesem Kasten wurde von Anthropic Claude erstellt auf der Grundlage eines kürzeren Textes von mir. Der KI-Text wurde anschließend von mir überarbeitet.
Darstellung von Klassen
Eine Klasse wird in einem Klassendiagramm mit einem dreigeteilten Kasten dargestellt.
Im oberen Teil steht der Klassenname (üblicherweise in Fettschrift und zentriert), im mittleren Teil sind die wichtigsten Instanzvariablen (auch Attribute genannt) aufgeführt, und im unteren Teil die wichtigsten Methoden (auch Operationen genannt).
In einem UML-Diagramm muss man nicht alle Instanzvariablen und Methoden aufführen, sondern nur die jeweils relevanten. Welche das sind, hängt vom Zweck des Diagramms ab: Geht es um einen Gesamtüberblick über ein System, genügen oft nur wenige Kernelemente; soll ein bestimmter Aspekt detailliert dokumentiert werden, zeigt man mehr. Attribute oder Methoden, die aus Gründen der Übersichtlichkeit weggelassen werden, werden durch drei Punkte (...) angedeutet.
Sichtbarkeit
Ein Plus-Zeichen (+) vor einer Instanzvariablen oder einer Methode bedeutet public, also öffentlich zugänglich von anderen Klassen aus. Ein Minuszeichen (-) steht für private- nur innerhalb der eigenen Klasse sichtbar. Darüber hinaus gibt es noch zwei weitere Sichtbarkeitsmodifikatoren: # steht für protected: sichtbar in der Klasse selbst und allen Unterklassen. Und ~ steht für package: sichtbar für alle Klassen innerhalb des Pakets.
Datentypen und Signaturen
Die Datentypen stehen - abgetrennt durch einen Doppelpunkt - hinter dem Bezeichner der Variable oder Methode. An diesem Beispiel sieht man gut, wie sehr sich die Syntax der UML-Diagramme von der Java-Syntax unterscheidet.
Bei Attributen gibt der Typ an, welche Art von Wert gespeichert wird, z.B.:
- teilgebiet : String
- niveau: int
- bewertung: double
Bei Methoden steht der Rückgabetyp hinter dem Klammerpaar, also nach den Parametern:
+ getName() : String
+ berechneGehalt(stunden: int, stundenlohn: double) : double
- zeigeDaten() : void
Parameter von Methoden
Die Parameter von Methoden kann man im Diagramm ganz weglassen, nur die Namen hinschreiben oder die Namen mit den Datentypen aufführen - man hat hier also sehr viele Freiheiten. Je nach Detailtiefe des Diagramms wählt man die passende Variante:
Keine Parameter: +berechneGehalt()
Nur Parameternamen: +berechneGehalt(stunden, stundenlohn)
Namen und Typen: + berechneGehalt(stunden: int, stundenlohn: double) : double
Beziehungen zwischen Klassen
Die durchgezogenen Pfeile mit den weißen Dreiecken am Ende symbolisieren die Vererbungsbeziehungen (IST-Beziehungen), während die gestrichelten Pfeile für Assoziationen stehen (HAT-Beziehungen).
Vererbung (Generalisierung / IST-Beziehung): Dargestellt durch einen durchgezogenen Pfeil mit einem weißen (hohlen) Dreieck an der Spitze, das auf die Oberklasse zeigt. Beispiel: Roman erbt von Buch. Man liest: "Ein Roman IST ein Buch."
Realisierung (Implementierung): Dargestellt durch einen gestrichelten Pfeil mit einem weißen Dreieck, der von der Klasse auf das implementierte Interface zeigt. Diesen Pfeil sieht man immer dann, wenn eine Klasse ein Interface umsetzt (z.B. ArrayList implementiert List).
Assoziation (HAT-Beziehung): Dargestellt durch einen gestrichelten Pfeil (oder auch eine einfache Linie mit Pfeil). Assoziationen zeigen, dass ein Objekt ein anderes kennt oder verwendet. Beispiel: Eine Bestellung kennt einen Kunden.
In dem Kasten über UML-Diagramme wird auf verschiedene Beziehungen zwischen Klassen eingegangen. Die Lexikon-Seite "Beziehungen" erläutert dieses Thema ausführlicher.