Java Weiterführende Arrays

Werbung
Java Weiterführende Arrays
Name
1
AnPr
Klasse
Datum
Einleitung
Für die Speicherung von gleichartigen Daten haben wir Arrays kennen gelernt. Diese eignen sich immer dann,
wenn wir eine feste Anzahl gleicher Datenstrukturen (entweder einfache Datentypen, oder Objekte) haben und
diese in einer Variablen ablegen möchten. Hierbei wurde folgender Syntax genutzt:
Wir erzeugen in diesem Code also
ein Array von 6 „int“ Werten, und
legen dieses Array in eine Variable
für „int“ – Arrays ab. Wir werden
bei dem Thema „Objektorientierung“ noch sehen, dass es durchaus sinnvoll sein kann, den Typen
der Arrayvariablen vom Typen des erzeugten Arrays abweichen zu lassen. Aber fürs erste lassen wir diese
beiden Datentypen identisch. Was wir auch gelernt haben war, wie man mehrdimensionale Arrays erzeugen
kann. Hierbei werden (für bspw. ein zweidimensionales Array) die beiden Dimensionsgrößen hintereinander
geschrieben:
Hier haben wir also ein Array
geschaffen, welches 6 Zeilen
und zwei Spalten aufweist –
wobei wir hier für uns einfach
definiert haben, dass die erste
Dimension für die Zeilen und
die zweite Dimension für Spalten steht.
Woher weiß ich eigentlich, was der Index für Zeilen und welcher für Spalten ist?
Antwort: Gar nicht! Es ist nämlich so, dass ein Array einfach nur Dimensionen hat. Ob ich die erste Dimension
nun Zeile, Spalte oder sonst wie nenne, ist rein meine Sache. Entscheidend ist hier, was ich im Array ablege
und somit wie ich die Daten interpretieren muss.
Soweit unser Vorwissen, mit dem wir schon ziemlich weit kommen. Es gibt aber Situationen, welche uns bei
diesen Arrays vor sehr schwierigen Aufgaben stellen:
 Ich habe pro Datensatz in der ersten Dimension nicht immer die gleiche Menge an Werten in der
zweiten Dimension. Z.B. in der ersten Dimension habe ich Teams und in der zweiten Teammitglieder,
wobei pro Team unterschiedliche Mitgliederanzahlen vorherrschen.
 Ich weiß beim Erzeugen des Arrays nicht, wie viele Einträge ich eigentlich benötige – die Arraygröße
soll also flexibel sein.
 Ich möchte nicht mit Hilfe der Indexposition auf das Array zugreifen, sondern mit Hilfe eines Schlüsselwertes.
All diese Anforderungen können vom Array direkt nicht bedient werden. Es wäre zwar Möglich mit Hilfe von
eigenen kleinen Unterprogrammen diese Aufgaben zu lösen, aber das würde mitunter umständlich werden.
Wir wollen uns nun mit Möglichkeiten beschäftigen, die uns Java für diese Problemstellungen bietet. Dies
wären:
 „nicht rechteckige“ Arrays
 ArrayList
 Assoziative Arrays
AnPr_ArrayPt2_v03.docx
Seite 1
Java Weiterführende Arrays
2
AnPr
Nicht rechteckige Arrays – oder alles Lüge bei n Dimensionen
Zuerst muss ich mal eine kleine „Notlüge“ bezüglich mehrdimensionaler Arrays in Java zugeben. Java kann
eigentlich gar keine mehrdimensionalen Arrays abbilden. Java tut nur so! Wir sehen uns hierzu mal folgenden
Code an:
Hier wird ein zweidimensionales Array erzeugt, indem zuerst ein eindimensionales Array erzeugt wird, welches wiederum Arrays aufnehmen kann. Pro Arrayposition der ersten Dimension wird nun ein neues Array
erzeugt, welches entsprechend die zweite Dimension darstellt. Wenn wir uns das Ganze nun Grafisch vorstellen, würde man das dergestalt
visualisieren,
dass man ein äußeres
Array mit (in diesem
Beispiel) 3 Einträgen
hat und pro Eintrag wiederum ein Array mit 6
Datenwerten hat.
Die wichtige Information ist nun, dass folgender Code exakt das gleiche Konstrukt liefert, wie der oben abgebildete:
int[][] my2DimArray = new int[3][6];
Dies hat nun weitreichende Konsequenzen. Sehen wir uns nun folgenden Code an und versuchen ihn zu interpretieren:
Das Besondere ist nun, dass man also aus einem zweidimensionalen Array eine Zeile einfach auslesen kann –
was im Prinzip lediglich das Ansprechen eines Eintrags des Arrays der obersten Dimension ist. In der Variable
„teamMitglieder“ steht somit folgendes Array:
{"Erna", "Franz", "Gustav", "Heinz"}.
Um nun zu beweisen, dass wir hier von ein und demselben Array sprechen, ergänzen wir den Code:
teamMitglieder[0] = "Erika";
System.out.println(teams[1][0]);
Seite 2
AnPr
Java Weiterführende Arrays
Wie Du siehst, wurde in dem herausgelösten Array der Name „Erna“ durch „Erika“ ersetzt. Bei der Ausgabe
wurde nun wieder auf das ursprüngliche zweidimensionale Array zugegriffen und siehe da – auch hier ist die
Erna nicht mehr da.
Diese Erkenntnis können wir nun nutzen, um den ursprünglichen Gedanken der „nicht rechteckigen“ Arrays
weiterzuverfolgen. Ändern wir unseren Code nun wie folgt ab:
Nun haben wir also die Situation, dass im Team auf Indexposition 1 fünf Einträge sind, also einer mehr als bei
den beiden anderen Teams. Abgesehen davon, dass das unfair ist, sollte uns dieses Beispiel klar machen, dass
wir also unterschiedlich große Einträge in den jeweiligen Positionen durchführen können.
Wir halten also fest:
 Ein Array kann mehrere Dimensionen aufweisen.
 In Java ist ein mehrdimensionales Array eine Ineinanderschachtelung von eindimensionalen Arrays.
 Die Größe der Arraydimensionen wird bei der Erzeugung des Arrays festgelegt - entweder mit dem Schlüsselwort „new“, oder als Konstantwerte in geschweiften Klammern.
 Bei einem Syntax „int[][] myArray = new int[3][6]“ wird ein „rechteckiges“ Array
angelegt, was soviel bedeutet wie „jede Zeile hat die gleiche Anzahl an Datensätzen“.
 Bei einem Syntax „int[][] myArray = new int[3][]“ wird ein eindimensionales Array
der Länge 3angelegt, welches pro Eintrag drei beliebig lange Integer Arrays aufnehmen
kann. Mit „myArray[0] = new int[6]“ wird in die erste Position des Arrays ein Integerarray der Länge 6 geschrieben.
 Wenn die Längen der Integerarrays unterschiedlich sind, spricht man von einem „nicht
rechteckigen“ Array.
Zusatzinfo:
Im Regelfall nutzen wir keine „nicht rechteckigen“ Arrays. Solche Arrays machen den Code nicht unbedingt
übersichtlicher. Trotzdem soll auf die Existenz hingewiesen werden, damit wir uns der Möglichkeit bewusst
sind.
3
ArrayList – wenn man hinterher erst schlauer ist
Sehr häufig kommt es vor, dass man dynamische Listen erstellen muss – also die
Größe der Liste sich während der Programmausführung ständig ändern kann.
Hier stoßen die Arrays (zumindest in Java) an ihre Grenzen. Skriptorientierte
Sprachen wie bspw. Javascript haben damit kein Problem, Java Programmierer
müssen sich hier etwas anderes ausdenken. Das Zauberwort hier heißt „ArrayList“. Dieses Konstrukt erlaubt es uns, zu jeder Zeit Einträge hinzuzufügen und
welche zu entfernen.
Da eine ArrayList eben eine ArrayList und kein Array ist, sehen die Zugriffsformen entsprechend anders aus als bei einem Array. Hier müssen wir ein paar neue
Methoden lernen, was aber recht intuitiv ist.
Seite 3
Java Weiterführende Arrays
AnPr
Folgender Code soll die Nutzung verdeutlichen:
Die ArraList Klasse versteckt sich in der Bibliothek „util“ und muss somit importiert werden. Danach erzeugen
wir ein ArrayList Objekt. Hier kommt ein neues Syntaxelement auf uns zu – die Typisierung der „raw“ Klasse.
Die ArrayList ist eigentlich so aufgebaut, dass sie jegliche Objekte aufnehmen kann und somit vom inneren
Aufbau her alle Daten vom Typ „Object“ verarbeitet. Das ist aber vom Handling her recht umständlich, da die
„get“ Methode demnach auch immer nur Elemente vom Typ „Object“ zurückgibt und wir für die Weiternutzung einen Typecast durchführen müssten. Dieser Typecast wird uns abgenommen, wenn wir bei der ArrayList
in spitzen Klammern den eigentlichen Datentyp eintragen – in unserem Beispiel eben <String>.
Wer nun schon etwas in die objektorientierte Programmierung hineingeschnuppert hat weiß nun aber, dass
ArrayList Objekte somit keine einfachen Datentypen aufnehmen können, sondern nur Objekte. Wer aber trotzdem bspw. ganze Zahlen in einer ArrayList ablegen möchte, muss mit den Wrapperklassen arbeiten:
ArrayList<Integer> myArrayList = new ArrayList<Integer>();
Nun können wir mit der Methode „add“ die einzelnen Elemente einfügen. Der „get“ Befehl liest sie wieder
aus, wobei der Parameter die Position in der ArrayList festlegt.
Wenn wir nun an einer bestimmten Position etwas einfügen, so müssen wir eine
andere „add“ Methode verwenden und zwar diejenige mit einem Indexparameter
(bspw. „add(3,"h")“). Hier wird dann ein Element an die entsprechende Position gesetzt. Aber Achtung – alle nachfolgenden Elemente verschieben sich dann
um eine Position. Gelöscht werden Elemente mit der „remove()“ Methode, welche ebenfalls die Indexposition als Parameter verlangt. Hier werden sich die nachfolgenden _Elemente entsprechend wieder nach links verschieben.
Wenn wir lediglich ein Element austauschen wollen, so müssen wir die Methode
„set“ verwenden. Folgende Methode würde also aus einer ArrayList den ersten
Eintrag durch das „H“ ersetzten:
myStringArrayList.set(0, "H");
Ich habe mal etwas von einem „Vector“ gehört. Ist das nicht das gleiche, wie eine ArrayList?
Antwort: Die wesentlichen Eigenschaften sind identisch. Der eigentliche Unterschied ist, dass der Vector
„synchronized“ ist, was bedeutet, dass verschiedene Threads sicher auf das Objekt zugreifen können, ohne
sich gegenseitig zu stören und somit Dateninkonsistenzen erzeugen. Für uns ist das jedoch (noch) nicht von
Relevanz und demnach empfehle ich (und auch Oracle…) die Nutzung von ArrayLists.
Seite 4
AnPr
Java Weiterführende Arrays
Wir halten also fest:
 Eine ArrayList kann dynamisch erweitert oder verkürzt werden.
 ArrayLists sollten bei der Deklaration den Typ der aufzunehmenden Klasse angeben.
Dies geschieht in spitzen Klammern: ArrayList<String>.
 Die Methode „add“ fügt ein Element in die ArrayList ein. Wird kein int-Parameter
angegeben, so geschieht es am Ende. Existiert ein int-Parameter, so sorgt er dafür, dass
das Element an der angegebenen Position eingefügt wird.
 Mit „remove“ wird das angegebene Element gelöscht.
 Mit „set“ kann ein Element ausgetauscht werden.
 Mit „get“ wird das angegebene Element ausgelesen. Wenn die Klasse angegeben
wurde, so hat das Element den angegebenen Datentyp.
Zusatzinfo:
Intern nutzt die ArrayList ein Array – was für uns allerdings verborgen bleibt. Lediglich die „capacity“ gibt
uns Rückschlüsse auf dieses Array. Es gibt bspw. einen Konstruktor, welcher die initiale „capacity“ vorgibt –
also die Größe des intern genutzten Arrays. Wenn man vorher schon ungefähr weiß, wie groß die Datenmenge
sein wird, kann das Performancevorteile bringen. Trotzdem kümmert sich Java automatisch um die Größenanpassung dieses Arrays, sollte der Platz nicht mehr ausreichend sein.
4
Assoziative Arrays – wer ist schon gerne nur eine Nummer?
Bis dato haben wir in Arrays stets die Indexposition benötigt, um an die einzelnen Elemente zu kommen. Dies
ist durchaus praktisch, da wir hiermit eine eindeutige Adressierung jedes einzelnen Eintrages haben und somit
unsere Elemente sicher identifizieren können. Es gibt aber Situationen, bei denen wir nicht über eine Indexposition zugreifen möchten, sondern bspw. über einen Namen oder ähnlichen Kennzeichnungen. Hier bieten sich
die sogenannten „assoziativen Arrays“ an. Java bietet gleich eine ganze Fülle solcher Arrays an:
 HashMap
 Hashtable (wie Hashmap, nur „synchronized“)
 LinkedHashMap (merkt sich die Einfügereihenfolge)
 TreeMap (bildet die Keys in einem schnell durchsuchbaren Baum ab)
In diesem Kapitel werden wir uns „nur“ mit der HashMap und HashTable beschäftigen, wobei das wesentliche
Handling der anderen beiden Klassen sich nicht wirklich unterscheidet – der Unterschied liegt eher in der
inneren Umsetzung.
Nun, wie können wir uns nun diese HashMaps vorstellen? Prinzipiell funktioniert das wie in einer Schulklasse.
Die Schüler sitzen mehr oder weniger
geordnet im Klassenzimmer. Wenn
der Lehrer jemanden ansprechen
möchte, dann sagt er nicht „Die Schülerin in der hintersten Reihe, dritte von
rechts solle sich melden“, sondern er
spricht sie mit dem Namen an.
Genau so läuft es mit den assoziativen
Arrays. Jeder Eintrag bekommt einen
Namen – eigentlich einen „Key“. Dieser muss somit eindeutig innerhalb des
Arrays sein, um den Eintrag eindeutig
identifizieren zu können. Man spricht
hier auch von „Schlüssel/Wert Paaren).
Das Besondere ist nun, dass wir jede Klasse, welche die Methoden „hashCode“ und „equals“ aufweist, als Key
verwenden können. „hashCode“ liefert einen Code, welcher als Schlüssel zum Auffinden des Elements verwendet wird und „equals“ kennen wir ja bereits aus der String Methode – hier wird auf inhaltliche Gleichheit
Seite 5
Java Weiterführende Arrays
AnPr
geprüft. Wir sehen hier wieder den Syntax mit den spitzen Klammern, welchen wir bei ArrayLists bereits
gesehen haben. Auch hier gilt, wir können in die HashMap nur Objekte eintragen, keine einfachen Datentypen.
Der oben stehende Code erzeugt eine HashMap. In diesem Beispiel habe ich den Key als String vorgegeben
und die Elemente (also die Werte, welche ich später suchen möchte) als „Integer“ – wir wollen somit zu jedem
Namen eine Zahl, bspw. das Alter, einfügen. Wenn wir später objektorientiert arbeiten, können wir als Wertelement beliebige Klassen vorsehen. Die Methode „put“ fügt ein Schlüssel/Wert Paar ein. Wo in der HashMap
dies geschieht ist für uns irrelevant. Wir haben ja den Key, der uns eindeutig zu unserem Wert führt.
Danach habe ich die Werte eingetragen, indem ich jedem Wert noch einen Schlüssel mitgegeben habe. Dieser
muss eindeutig sein – taucht ein Schlüssel ein zweites mal auf, so wird der vorausgegangene Wert einfach
überschrieben. Im Code habe ich nun eine Stringvariable „myKey“ deklariert und mit einem Schlüsselwert
belegt – hier der Name „Frieda“. Nun fischen wir uns den Eintrag von Frieda mit folgendem Code aus unserer
HashMap:
myHashMap.get(myKey);
Schon haben wir das Alter von Frieda herausgefunden.
Was passiert eigentlich, wenn ich in dem oberen Beispiel einen Schlüssel suche, der gar nicht existiert, wie
bspw. „Paul“?
Antwort: Die HashMap würde den Wert null liefern. Da wir ja eine Integerklasse als Wert (und nicht der
einfache Datentyp „int“) vorgesehen haben, kann die HashMap auch null zurückliefern.
Nun wollen wir uns noch mal ein paar nützliche Methoden der HashMap ansehen, welche uns den Umgang
mit den Hashmaps erleichtern werden. Beginnen wir mit den „contains“ Methoden. Diese zeigen uns an, ob
ein Key oder ein Value existiert:
myHashMap.containsKey("Frieda");
myHashMap.containsValue(21);
Beide Methoden liefern einen boolean Wert zurück. „containsKey“ liefert „true“, wenn das angegebene Argument (in unserem Beispiel „Frieda“) als Schlüssel in der HashMap existiert. „containsValue“ liefert uns „true“
zurück, wenn der angegebene Wert (in unserem Beispiel „21) mindestens einmal als Wert existiert.
Was ebenfalls sehr von Nutzen sein kann, ist die Erzeugung einer „Enumeration“. Diese Funktion wird allerdings nicht von HashMaps unterstützt – hier muss man über Collections gehen, was aber umständlicher ist.
Eine Enumeration ist ein Objekt, welches mir ermöglicht sämtliche Elemente einer Hashtable sequenziell abzuarbeiten. Sehen wir uns hierfür folgenden Code an, welcher im Wesentlichen dem oberen gleicht, es wird
lediglich mit einer Hashtable gearbeitet und über eine Enumeration ausgelesen.
Seite 6
AnPr
Java Weiterführende Arrays
Der obere Teil ist also Identisch mit dem Code der HashMap, nur dass wir eine Hashtable nutzen. Nachdem
wir unsere Hashtable mit Werten belegt haben, erzeugen wir ein Enumeration Objekt, welches im Prinzip eine
Aneinanderreihung von Elementen ist, auf die ich sequenziell zugreifen kann. Zwei wesentliche Methoden
sind zu beachten:
nextElement() liefert das aktuelle Element und schiebt den Zeiger auf das nächste Element weiter.
hasMoreElements() ist solange auf true, wie noch nicht alle Elemente ausgelesen wurden.
Wer alle Schlüssel aus einer Hashtable auslesen möchte, der kann auch diese in eine Enumeration laden:
myHashtable.keys()
Entfernt werden die Elemente wiederum über den Key:
myHashtable.remove(key);
Wir halten also fest:
 Assoziative Arrays bilden Schlüssel/Wertpaare ab.
 Sowohl die Schlüssel, als auch die Werte müssen „Objekte“ sein. Aus den Schlüsseln
müssen sich „Hashes“ erzeugen lassen können. Sie müssen innerhalb des Arrays eindeutig sein.
 Mit „put“ werden die Schlüssel/Wertpaare abgelegt.
 Mit „get(key)“ werden die Werte zum vorgegebenen Schlüssel ausgelesen.
 Mit „remove(key)“ wird das Element zum angegebenen Schlüssel gelöscht.
 Man kann mit „containsKey“ bzw. „containsValue“ feststellen, ob ein gegebener
Schlüssel bzw. ein gegebener Wert in dem Array vorhanden ist.
 Bei Hashtables können alle Werte, bzw. alle Schlüssel sequenziell ausgelesen werden,
indem man mit „.elements()“ bzw. „.keys()“ eine Enumeration erzeugt.
 Diese Enumerations liest man sequenziell mit der Methode „nextElement()“ aus, bis
die Methode „hasMoreElements()“ den Wert „false“ liefert.
Zusatzinfo:
Auch die Hashtable bietet einen Konstruktor mit einer initialen Kapazität an. Darüber hinaus gibt es noch den
Wert des „loadFactors“, was im Wesentlichen angibt, bei welchem Füllgrad sich Java um die Erhöhung der
Kapazität kümmert. Wer spezielles Verhalten seiner Hashtable bzw. Hashmap haben möchte, der sollte sich
die Doku zum loadFactor ansehen – für die meisten Nutzer reicht der normale Konstruktor ohne Parameter
völlig aus.
Seite 7
Java Weiterführende Arrays
5
AnPr
Lizenz
Diese(s) Werk bzw. Inhalt von Maik Aicher (www.codeconcert.de) steht unter einer
Creative Commons Namensnennung - Nicht-kommerziell - Weitergabe unter gleichen
Bedingungen 3.0 Unported Lizenz.
Seite 8
Herunterladen