1. Zielsetzung
Wir wollen ein Array erstellen, in dem wir Objekte der Klasse Buch speichern können. Später werden wir die Klasse Buch durch verschiedene Unterklassen erweitern. Dabei werden wir sehen, dass in einem Buch-Array auch Objekte von Unterklassen von Buch gespeichert werden können. An diesem Beispiel vertiefen wir das bereits in Folge 6 eingeführte Prinzip der Polymorphie.
2. Die Klasse Buch, Version 1
Quelltext
public class Buch
{
private String titel, autor;
private int jahr;
public Buch(String titel, String autor, int jahr)
{
this.titel = titel;
this.autor = autor;
this.jahr = jahr;
}
public void zeige()
{
System.out.println("---------------------------------");
System.out.println("Titel : " + titel);
System.out.println("Autor : " + autor);
System.out.println("Jahr : " + jahr);
System.out.println("---------------------------------");
System.out.println();
}
}
Die erste Version der Klasse Buch ist noch recht einfach aufgebaut: Sie besitzt die drei Instanzvariablen titel, autor und jahr, einen Konstruktor, der diese Instanzvariablen initialisiert, und eine Methode zeige(), welche die Werte der Instanzvariablen in der Konsole anzeigt.
Was hier noch fehlt, sind Setter- und Getter-Methoden.
Checkende Setter-Methoden
setAutor()
public void setAutor(String autor)
{
if (autor == null || autor.isBlank())
this.autor = "unbekannter Autor";
else
this.autor = autor;
}
setTitel()
public void setTitel(String titel)
{
if (titel == null || titel.isBlank())
this.titel = "unbekannter Buchtitel";
else
this.titel = titel;
}
setJahr()
public void setJahr(int jahr)
{
if (jahr < 1500)
this.jahr = 1500;
else
this.jahr = jahr;
}
Die Setter-Methoden übernehmen hier nicht einfach jeden übergebenen Wert, sondern überprüfen ihn zunächst. Wenn bei setAutor() oder setTitel() als Parameter null übergeben wird oder wenn der übergebene String leer ist bzw. nur aus Leerzeichen besteht, wird stattdessen "unbekannter Autor" bzw. "unbekannter Buchtitel" als Wert für die entsprechende Instanzvariable eingetragen.
Bei setJahr() werden Jahreszahlen vor 1500 nicht übernommen. Stattdessen wird in diesem Fall der Wert 1500 eingetragen.
Mit solchen kontrollierenden Setter-Methoden können wir auch den Konstruktor der Klasse etwas umgestalten:
public Buch(String titel, String autor, int jahr)
{
setTitel(titel);
setAutor(autor);
setJahr(jahr);
}
Dieses Vorgehen hat einen wichtigen Vorteil: Die Überprüfung der übergebenen Werte muss nicht zweimal programmiert werden. Der Konstruktor verwendet dieselben Methoden zur Kontrolle der Werte, die auch später beim Ändern eines Buch-Objekts eingesetzt werden können.
In professionellen Programmen würde man bei ungültigen Werten häufig anders reagieren, beispielsweise durch das Auslösen einer Exception. Mit Exception-Handling beschäftigen wir uns in diesem Kurs jedoch nicht. Daher verwenden unsere Setter-Methoden bei ungültigen Werten einfach sinnvolle Ersatzwerte.
Getter-Methoden
Die drei Getter-Methoden für die Instanzvariablen sind recht einfach zu implementieren, wie das folgende Beispiel zeigt:
Beispiel getAutor()
public String getAutor()
{
return autor;
}
Bei den Getter-Methoden müssen wir keine weiteren Prüfungen durchführen, daher sind sie recht einfach gestaltet.
3. Die Klasse Buchliste
Objektarrays haben wir bereits kennengelernt. Daher sollte es nicht allzu schwer sein, ein Array zu konstruieren, das Objekte der Klasse Buch speichert.
Quelltext
public class Buchliste
{
private Buch[] liste;
public Buchliste()
{
liste = new Buch[4];
erzeuge();
zeige();
}
public void erzeuge()
{
liste[0] = new Buch("Einführung in Java", "Meier, Otto", 2025);
liste[1] = new Buch("Lexikon der Informatik", "Duden-Verlag", 2012);
liste[2] = new Buch("Der Herr der Ringe", "J.R. Tolkien", 1964);
liste[3] = new Buch("Prinzipien der OOP", "Müller, Jonathan", 2020);
}
public void zeige()
{
for (int i = 0; i < liste.length; i++)
{
liste[i].zeige();
}
}
public static void main(String[] args)
{
new Buchliste();
}
}
Wenn Sie BlueJ als Entwicklungsumgebung benutzen, wie es in diesem Kurs ja eigentlich vorgesehen ist, können Sie auf die main()-Methode verzichten. Mit BlueJ legen Sie einfach ein neues Objekt der Klasse Buchliste an. Da der Konstruktor bereits die Methoden erzeuge() und zeige() aufruft, müssen Sie nichts mehr machen.
Die main()-Methode ist erforderlich, wenn Sie das Programm außerhalb von BlueJ starten möchten, beispielsweise in IntelliJ. Die main()-Methode übernimmt dann gewissermaßen die "Klickarbeit" aus BlueJ und erzeugt selbstständig ein neues Objekt der Klasse Buchliste.
In der Methode erzeuge() ist der Einsatz einer for-Schleife nicht sinnvoll, da jedes Buch-Objekt unterschiedliche Daten erhält. Ein ähnliches Problem hatten wir bereits bei der Erzeugung der verschiedenen Gegenstand-Objekte in dem kleinen Adventure-Projekt. Daher erzeugen wir hier jedes Buch-Objekt einzeln.
Bei der zeige()-Methode dagegen lohnt sich der Einsatz einer for-Schleife, da hier nur die zeige()-Methoden der entsprechenden Arrayelemente aufgerufen werden müssen.
4. Unterklassen von Buch
Wir gehen jetzt - ganz im Sinne der Spezialisierung - von der Klasse Buch aus und erstellen die beiden Unterklassen Roman und Sachbuch. Die gemeinsamen Eigenschaften und Methoden bleiben in der gemeinsamen Oberklasse Buch. Die beiden Unterklassen ergänzen dann jeweils ihre speziellen Eigenschaften.
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
{
private 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
{
private 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);
}
}
Da wir bereits in der Folge 6 über Klassen und Unterklassen gesprochen haben, müssen wir die Quelltexte der beiden Unterklassen Roman und Sachbuch hier nicht mehr erläutern.
Die Version 2 der Klasse Buchliste
public class Buchliste
{
private Buch[] liste;
public Buchliste()
{
liste = new Buch[5];
erzeuge();
zeige();
}
public void erzeuge()
{
liste[0] = new Sachbuch("Einführung in Java", "Meier, Otto", 2025, "Informatik");
liste[1] = new Sachbuch("Lexikon der Informatik", "Duden-Verlag", 2012, "Informatik");
liste[2] = new Buch("Landschaften Deutschlands", "Tegeler, Jürgen", 2020);
liste[3] = new Roman("Der Herr der Ringe", "J.R. Tolkien", 1964, "Fantasy");
liste[4] = new Sachbuch("Biochemie der Pflanze", "Nultsch, Karl", 1987, "Biochemie");
}
public void zeige()
{
for (int i = 0; i < liste.length; i++)
{
liste[i].zeige();
}
}
}
In der Methode erzeuge() werden in dem Array ein Buch-, ein Roman- und drei Sachbuch-Objekte gespeichert.
Die zeige()-Methode durchläuft das Array Element für Element und ruft dabei für jedes Objekt die passende zeige()-Methode auf.
Das erste Arrayelement liste[0] enthält eine Referenz auf ein Objekt der Klasse Sachbuch. Die Schleife in der Methode zeige() führt daher für dieses Objekt die überschriebene Methode Sachbuch.zeige() aus.
Das dritte Arrayelement ist ein Objekt der Klasse Buch, entsprechend wird beim Anzeigen Buch.zeige() ausgeführt.
Beim vierten Arrayelement wird Roman.zeige() ausgeführt und beim fünften Element wieder Sachbuch.zeige().
Polymorphie
Eine Referenz der Oberklasse Buch kann zur Laufzeit auch auf Objekte der Unterklassen Roman und Sachbuch verweisen. Beim Aufruf einer überschriebenen Methode wird dann automatisch die zum tatsächlichen Objekt gehörende Methode ausgeführt.
Das Array liste hat den Typ Buch[]. Seine Elemente können daher Referenzen auf Objekte der Klasse Buch oder auf Objekte von Unterklassen der Klasse Buch aufnehmen. Referenzen auf Objekte anderer Klassen können jedoch nicht in liste gespeichert werden.
Falls wir aber später einmal weitere Unterklassen von Buch erstellen (oder Unterklassen von Roman oder Sachbuch), dann können Referenzen auf Objekte dieser Unterklassen durchaus in dem Array liste gespeichert werden.
Polymorphie beim Durchlaufen der Liste
for (int i = 0; i < liste.length; i++)
{
liste[i].zeige();
}
Beim Aufruf liste[i].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. Java führt für jedes Arrayelement automatisch die zeige()-Methode aus, die zum tatsächlichen Objekt gehört.
Ein Vorteil dieses Verfahrens: Wenn wir weitere Unterklassen von Buch anlegen, beispielsweise Fachbuch oder Lexikon, dann kann die obige for-Schleife unverändert übernommen werden.
Aufgabe
Überprüfen Sie die obige Behauptung, dass die for-Schleife der zeige()-Methode unverändert übernommen werden kann, indem Sie eine zusätzliche Unterklasse von Buch erstellen, beispielsweise Lexikon, und zwei Objekte dieser neuen Unterklasse in die Liste liste[] mit aufnehmen.
Die Größe von liste[] müssen Sie natürlich vorher erhöhen, damit die beiden neuen Objekte aufgenommen werden können.