# 1. Lineare Datenstrukturen Kommen Datenstrukturen zum Einsatz, geht es meist darum, beliebig viele Objekte in einer passenden Datenstruktur zu verwalten, z.B. alle Schüler einer Schule. In einem solchen Falle macht es keinen Sinn, alle Eigenschaften eines Schülers als primitive Datentypen zu modellieren und für jede Eigenschaft ein eigenes Feld o.ä. zu bilden. Deshalb wird zu Beginn dieses Kapitels zunächst erläutert, wie Objekte als Datensatzspeicher verwendet werden können. Im Anschluss daran werden die linearen Datenstrukturen Feld, Liste, Stack und Queue behandelt. ## Objekte vs. primitive Datentypen ### Objekte als Datenspeicher In diesem Abschnitt möchten wir eine Schülerverwaltung modellieren. Dazu entwerfen wir eine eigene Klasse *Schueler* und modellieren zunächst die benötigten Eigenschaften als Attribute. Bei Bedarf kommen noch Methoden zum Ändern und Abfragen der Attribute hinzu. Im vorliegenden Beispiel sind die beiden Attribute Alter und Name definiert worden. Beide müssen im beim Erzeugen eines Schüler-Objekts übergeben werden (vgl. Konstruktor), beide können mithilfe der set-Methoden auch nachträglich verändert und mithilfe der get-Methoden jederzeit abgefragt werden. ```java public class Schueler { private String name; private int alter; public Schueler(String name, int alter) { this.name = name; this.alter = alter; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setAlter(int alter) { this.alter = alter; } public int getAlter() { return alter; } } ``` Objekte einer Klasse, die wie in diesem Beispiel lediglich als Datenspeicher fungieren und selbst keine oder fast keine eigene Programmlogik besitzen, werden in Java als **Beans** ("Kaffeebohnen") bezeichnet. ### Wrapper-Klassen Die einzige Datenstruktur, die in Java neben Objekten auch primitive Datentypen verwalten kann, ist das Feld. Alle anderen Datenstrukturen (z.B. Liste, Stack, Queue usw.) erlauben lediglich das Speichern von Objekten. Doch was ist zu tun, wenn es für eine Anwendung ausreicht, lediglich Zahlen (z.B. ISBN-Nummern oder Schuhgrößen) zu speichern? Muss dann erst umständlich eine wie im Schüler-Beispiel beschriebene eigene Klasse entworfen werden? Für diese seltenen Fälle sind in Java so genannte **Wrapper-Klassen** eingebaut. primitiver Datentyp | Wrapper-Klasse | Service-Methode -----|-----|----- int | Integer | intValue() double | Double | doubleValue() char | Character | charValue() boolean | Boolean | boolValue() Beispiel: Ein primitiver int-Wert 17 wird in einem Objekt der Klasse Integer "verpackt" (Boxing). ```java int i = 17; Integer iobj = new Integer(i); // Boxing ``` Um ihn anschließend wieder aus dem Objekt herauszulesen (Unboxing), steht für jede Wrapper-Klasse eine entsprechende Service-Methode (vgl. Tabelle) zur Verfügung. ```java int j = iobj.intValue(); // Unboxing ``` ## Feld Die naheliegende Datenstruktur zur Verwaltung von Objekten ist das Feld. Es ist in Java bereits eingebaut und ist recht einfach zu nutzen. ### Verwendung eines Felds An dieser Stelle soll das Beispiel einer Schülerverwaltung wieder aufgegriffen werden. Eine entsprechende Klasse Schüler ist bereits im vorherigen Abschnitt (vgl. [Objekte als Datenspeicher](#objekte-als-datenspeicher)) entwickelt worden. Die Erzeugung und Verwendung eines Feld über Schüler-Objekten verläuft analog zur Verwendung eines Felds über primitiven Datentypen (vgl. auch Abschnitt [Felder](../grundlagen/javagrundlagen.md#felder)). Wie im Quelltext zu sehen ist, werden zunächst die Schüler-Objekte erzeugt und anschließend wie in einem Feld üblich an die entsprechenden Positionen gesetzt. Die Methode *istVorhanden()* zeigt exemplarisch den Durchlauf durch ein Feld. ```java public class Schuelerverwaltung { private Schueler[] meineschueler; public Schuelerverwaltung() { Schueler s1 = new Schueler("Otto",14); Schueler s2 = new Schueler("Susi",13); Schueler s3 = new Schueler("Hans",15); meineschueler = new Schueler[3]; meineschueler[0] = s1; meineschueler[1] = s2; meineschueler[2] = s3; } public boolean istVorhanden(String suchname) { for (int i=0; i l = new List(); l.append(s1); l.append(s2); l.append(s3); ``` ### Eine Liste durchlaufen Beim Durchlaufen einer Liste (z.B. um eine Suche zu realisieren) ist ein Sprung an den Anfang der Liste mithilfe der Methode *toFirst()* erforderlich. Anschließend hilft die Anfrage *hasAccess()* dabei zu erfragen, ob noch ein Listenelement verfügbar ist oder durch das Weiterlaufen durch die Liste mit der Methode *next()* bereits das Ende der Liste erreicht ist. Wird die Zeichenketten-Liste aus dem vorherigen Abschnitt zugrunde gelegt, so lautet die Ausgabe der Schleife: *Babsi -> Franz -> Susi -> ENDE!* ```java l.toFirst(); while (l.hasAccess()) { String s = l.getContent(); System.out.print(s+" -> "); l.next(); } System.out.println("ENDE!"); ``` ### Eine Liste verändern Für das nächste Beispiel stellen wir uns eine Musiksammlung vor, in der Objekte der Klasse *Titel* verwaltet werden. Für jedes Titelobjekt ist eine Bewertung zwischen 1 und 5 angegeben, die durch die Methode *getBewertung()* erfragt werden kann. Damit durchläuft die Schleife die Liste aller Titelobjekte und löscht mit der Methode *remove()* diejenigen Titel, die eine Bewertung 1 erhalten haben. Zusätzlich wird die Anzahl der gelöschten Objekte in der Variablen *counter* mitgezählt. ```java l.toFirst(); int counter = 0; while (l.hasAccess()) { Titel t = l.getContent(); if (t.getBewertung()==1) { l.remove(); counter++; } else { l.next(); } } ``` ### In eine sortierte Liste einfügen Eine Sortierung einer Liste kann mitunter ebenfalls sinnvoll sein. In unserem Beispiel der Musiksammlung könnten die Titelobjekte sortiert nach der Bewertung abgelegt werden. Damit die Sortierung der Liste nicht verloren geht, muss dem Benutzer der Musiksammlung eine eigene Methode zum sortierten Einfügen eines neuen Titels zur Verfügung gestellt werden. Die Schleife wird solange durchlaufen, bis ein Titel gefunden wurde, der eine höhere oder gleich hohe Bewertung als der neue Titel besitzt. An dieser Stelle wird das zugehörige Titelobjekt mit der Methode *insert* vor dem aktuellen Listenobjekt eingefügt. ```java public void fuegeTitelSortiertEin(Titel t, List l) { l.toFirst(); while (l.hasAccess()) { Titel aktuell = l.getContent(); if (t.getBewertung() <= aktuell.getBewertung()) { l.insert(t); return; } else { l.next(); } } l.append(t); } ``` ## Stack und Queue Als Spezialfall der Liste sind sich Stack und Queue sehr ähnlich, weshalb sie oft gemeinsam besprochen werden. Die Queue (Schlange) ist eine FIFO-Datenstruktur (first in, first out) - die Objekte werden in der Reihenfolge verwaltet, in der sie in die Datenstruktur eingefügt worden sind. Direkter Zugriff besteht nur auf das vorderste Element der Schlange (den Kopf), eingefügt wird immer hinten (am Schlangenende). Der Stack (Stapel) hingegen wird als LIFO-Datenstruktur (last in, first out) bezeichnet. Hier werden neue Objekte immer oben auf den Stapel gelegt, direkter Zugriff besteht ebenfalls nur auf das oberste Objekt des Stapels. ### Einen Stack aufbauen Wie bei einer Liste ist beim Erzeugen eines Stacks zunächst die Klasse der Objekte anzugeben, die im Stack verwaltet werden sollen. Im nachfolgenden Beispiel werden drei Zeichenketten auf den Stack gelegt. Durch die LIFO-Struktur ergibt sich auf dem Stapel von oben nach unten gesehen die Abfolge *Hans - Maria - Manfred*. ```java String s1 = "Manfred"; String s2 = "Maria"; String s3 = "Hans"; Stack s = new Stack(); s.push(s1); s.push(s2); s.push(s3); ``` ### Einen Stack durchlaufen Der Stapel kann mit einer Schleife leicht von oben nach unten durchlaufen werden. !!! Hinweis Dabei wird der Stapel abgebaut, d.h. der Zugriff auf die Objekte geht verloren. Ist dies unerwünscht, müssen die einzelnen Objekte während des Durchlaufs in einer anderen Datenstruktur zwischengespeichert werden. ```java while (!s.isEmpty()) { String aktuell = s.top(); System.out.println(aktuell); s.pop(); } ``` ### Eine Queue aufbauen Wie bei einer Liste ist beim Erzeugen einer Queue zunächst die Klasse der Objekte anzugeben, die in der Queue verwaltet werden sollen. Im nachfolgenden Beispiel werden drei Zeichenketten in die Schlange eingefügt. Durch die FIFO-Struktur ergibt sich in der Schlange die Abfolge *Manfred - Maria - Hans*. ```java String s1 = "Manfred"; String s2 = "Maria"; String s3 = "Hans"; Queue s = new Queue(); s.enqueue(s1); s.enqueue(s2); s.enqueue(s3); ``` ### Eine Queue durchlaufen Eine Schlange kann mit einer Schleife leicht von vorn nach hinten durchlaufen werden. !!! Hinweis Dabei wird die Schlange abgebaut, d.h. der Zugriff auf die Objekte geht verloren. Ist dies unerwünscht, müssen die einzelnen Objekte während des Durchlaufs in einer anderen Datenstruktur zwischengespeichert werden. ```java while (!s.isEmpty()) { String aktuell = s.front(); System.out.println(aktuell); s.dequeue(); } ```