Arrays: ein rote-Pillen-Kapitel
Vielleicht hast du den Film Matrix gesehen? Ich meine den ersten Teil…
Dann kannst du dich vielleicht an folgende Frage von Morpheus erinnern:
In dem Film geht es um eine von Maschinen vorgegaukelte Welt, und die Hauptfigur Neo wird vor die Wahl gestellt, diese Welt zu erforschen, oder weiter in ihr zu leben. Morpheus stellt ihn vor die Wahl:
You take the blue pill… the story ends, you wake up in your bed and believe whatever you want to believe. You take the red pill… you stay in Wonderland, and I show you how deep the rabbit hole goes.
Genau! Im Film stellt sich heraus, dass die Welt eigentlich aus einem Haufen Computerprogrammen besteht, und Neo lernt, damit umzugehen. Er entscheidet sich für die rote Pille.
Dieses Kapitel ist in gewisser Weise ein Rote-Pille-Kapitel. Du wirst lernen, was der ArrayList, die wir bisher einfach so verwendet haben, eigentlich zu Grunde liegt.
Das kommt darauf an. Wenn du mit der Benutzung der ArrayList klar kommst, eigentlich nicht. Dann kannst du dieses und das nächste Kapitel überspringen.
Wenn du allerdings etwas genauer erfahren möchtest, wie dein Rechner Daten im Hauptspeicher abspeichert oder deine Java-Programme manchmal ganz schnell laufen müssen, dann solltest du weiter lesen. Manchmal bekommst du Arrays zurück, wenn du bestimmte String-Methoden aufrufst. Auch bestimmte Anwendungen wie z.B. Spielfelder wie ein Schachbrett oder ein Tic-Tac-Toe-Feld, oder eine Spiel-Landkarte nutzen typischerweise Arrays. (Oder sogar: Arrays von Arrays… – dazu mehr im nächsten Kapitel.)
Herzlichen Glückwunsch zu dieser Entscheidung!
Arrays sind Exoten in Java
Objekte sind wichtig in Java, fast alles dreht sich um sie. Und obwohl Arrays in Java ebenfalls Objekte sind, werden sie anders verwendet als andere Objekte.
Das werden wir uns nun genau ansehen. Wir starten mit der…
Erzeugung von Arrays
Eine ArrayList legen wir mit ihrem Datentyp zusammen an (Deklaration), und erzeugen sie, indem wir den Konstruktor der ArrayList-Klasse aufrufen – abzulesen an den runden Klammern. Hier die ganze Erzeugung in zwei Zeilen:
ArrayList<String> wörter; // Deklaration
wörter = new ArrayList<>(); // Initialisierung
Bei Arrays schreiben wir statt des Namens nun die Symbole [
und ]
(also: eckige Klammern). Auf einer deutschen Tastatur kannst du diese erzeugen, indem du Alt Gr+8 bzw. Alt Gr+9 tippst. Diese Klammern nach einem beliebigen Datentyp eingetippt, und schon hast du ein Array deklariert!
Zusammen mit der Initialisierung sieht das dann so aus:
String[] wörter; // Deklaration
wörter = new String[6]; // Initialisierung
Eine Zahl (es könnte auch eine andere sein) ist nötig, um die Größe des Arrays festzulegen – im Unterschied zur ArrayList lässt sich nämlich beim Array die Größe nachträglich nicht mehr verändern!
Das würde zu einer Fehlermeldung führen, ähnlich dieser:
error: cannot find symbol
wörter.add("Red Pill");
^
Zwar ist ein Array in Java ein Objekt, doch stellen Array-Objekte keine Methode add
zur Verfügung. Da das Array nicht wachsen kann, wäre das auch gar nicht möglich…
Das würde zur gleichen Fehlermeldung führen… Zum Zugriff auf einzelne Elemente kommen wir auch gleich. Für die Erzeugung von Arrays gibt es nämlich noch eine schöne Kurzschreibweise:
String[] wörter = {"how", "deep", "the", "rabbit", "hole", "goes"};
Auch wenn Arrays in Java etwas exotisch sind, so sind sie doch dafür eingerichtet, mit ihrer speziellen Syntax (d.h.: wie man sie anspricht) schnell und eigentlich unkompliziert handhabbar zu sein.
Das zeigt sich auch beim…
Zugriff auf einzelne Elemente
Hier ähneln Arrays eher Variablen. Um einem bestimmten Element (an einem bestimmten Index) einen Wert zuzuweisen, wird einfach das =
verwendet. Etwa so:
wörter[2] = "Gurkensalat";
In dieser Hinsicht ist das Array konsequent. get
geht ebenfalls nicht…
Aber es ist nicht sehr schwierig, wenn du dich einmal an die eckigen Klammern gewöhnt hast. Deinen Gurkensalat kannst du am Bildschirm ausgeben, wenn du die gleiche Schreibweise ohne =
nutzt:
println(wörter[2]);
Es ist nicht überall anders…
Iterieren über Arrays
Es ist nicht völlig anders. Die erweiterte for
-Wiederholung lässt sich ohne weiteres nutzen:
for (String wort : wörter) {
println(wort);
}
Etwas anders sieht es mit der herkömmlichen for
-Wiederholung aus.
Aber nicht viel… Siehst du den Unterschied?
for (int i=0; i<wörter.length; i++) {
println(wörter[i]);
}
Was ist hier anders als beim Iterieren über eine ArrayList?
Doch, es ist etwas anders! Statt der Methode size()
wird hier nämlich das Attribut length
ausgelesen.
Das ist kein Fehler! length
ist keine Methode, sondern ein Attribut des Array-Objekts. Weil dieses Attribut allerdings nicht wie sonst üblich privat ist, sondern von außen sichtbar, lässt es sich einfach so auslesen.
Nein, das geht nicht. Auch wenn das Attribut sichtbar ist, lässt es sich nicht verändern. (Es ist, wie die Programmierer_innen sagen, final.)
Das ist richtig!
Arrays haben also keine (aufwendigere) Methode wie size()
, sondern ein (einfacheres) Attribut length
– ein weiterer Unterschied zur ArrayList.
Was Array und ArrayList miteinander zu tun haben
Nun stoßen wir ein wenig in das rabbit hole vor!
Arrays mit ihren merkwürdigen Eigenschaften (können nicht größer werden, haben keine Methoden, sind über Indizes erreichbar) sind ziemlich nah dran an der Art und Weise, wie übliche Computer Daten in ihrem Hauptspeicher vorhalten.
Ja! Wenn ein Programm eine Reihe von Daten im Hauptspeicher ablegen soll, muss erst ein Bereich dafür reserviert werden. (Man sagt auch: es wird Speicher allokiert.) Weil über einfache Adressen (ähnlich den Hausnummern einer Straße) darauf zugegriffen werden können soll, liegen diese Speicherblöcke alle nebeneinander.
Den, dass sich die »Hausnummer« (also die Speicheradresse) über den Index leicht ausrechnen lässt. Der Prozessor weiß, wie viel Platz jedes Array-Element einnimmt, muss dann nur noch den Index mit diesem Platzbedarf malnehmen, das Ergebnis zur Startadresse hinzuzählen, und kommt direkt beim gewünschten Element heraus – das geht sehr schnell. Es gibt allerdings auch einen Nachteil…
Bei modernen Computern sind meistens viele Programme aktiv – von vielen bekommen wir nichts oder nur wenig mit. Allen ist aber gemeinsam, dass sie Platz im Hauptspeicher benötigen. Weil das so ist, kann es sein, dass nach dem von einem Array reservierten Bereich ein anderes Programm Platz beansprucht. Deshalb kann das Array nicht nachträglich größer werden.
Der ArrayList liegt ein Array zu Grunde. Wenn du eine ArrayList anlegst, wird das ArrayList-Objekt heimlich 😎 ein Array anlegen, und dort die Elemente speichern.
Nein. Standardmäßig wird ein solches Array beispielsweise die Größe 10 haben. Wenn die ArrayList darüber hinauswächst, wird an einer anderen Stelle ein doppelt so hoher Bereich reserviert, und das ganze Array dorthin kopiert. Wenn dieser Bereich wieder voll sein sollte, wird ein wieder doppelt so großer Bereich reserviert, und es findet die nächste Kopieraktion statt – und so weiter… (Weil dieses Verhalten nicht sichtbar ist, könnte die Erweiterung allerdings auch anders gelöst sein – ein Array liegt aber immer zu Grunde.)
Um die Vorteile, die du schon kennst, nutzen zu können. Die ArrayList kann größer werden, und trotzdem kann ich schnell auf einzelne Elemente zugreifen. Nur wenn sie gerade wachsen muss, kann es etwas dauern…
Das war nun eine Menge Stoff… Möchtest du noch ein lustiges Beispiel sehen?
Eine Anwendung: Der Ulkige-Tier-Generator
Schreibe diesen Code in eine Datei TierGenerator.java in einem neuen Workspace in der Online-IDE:
public class TierGenerator {
private String[] adjektive = { "gemeine ", "flossenfüßige ", "kleine ", "echte " };
private String[] vorsilben = { "Buckel", "Ohren", "Stummelschwanz", "Schuppenkriech" };
private String[] nachsilben = { "wale", "robben", "chamäleons", "tiere" };
public String erzeugeTier() {
String tier = "";
int zufallsIndexAdjektiv = Random.randint(0, adjektive.length - 1);
tier += adjektive[zufallsIndexAdjektiv];
int zufallsIndexVorsilben = Random.randint(0, vorsilben.length - 1);
tier += vorsilben[zufallsIndexVorsilben];
int zufallsIndexNachsilben = Random.randint(0, nachsilben.length - 1);
tier += nachsilben[zufallsIndexNachsilben];
return tier;
}
}
Füge außerdem eine Datei Start hinzu:
TierGenerator gen = new TierGenerator();
for (int i = 0; i < 10; i++) {
println(gen.erzeugeTier());
}
Du kannst mit diesem Programm einige Dinge versuchen:
Versuche zu verstehen, was in jeder Zeile passiert.
Du kannst das mit der beliebten Rubber Ducking-Technik machen…
Wenn du etwas nicht verstehst, ruf den Menüpunkt Api-Verzeichnis im Hilfe-Menü auf, und suche die Klasse, die dir Probleme bereitet
Lass das Programm (auch schrittweise) laufen.
Verändere das Programm, und beobachte, wie sich deine Veränderungen auswirken.
Das Array- oder ArrayList-Quiz
Nun kannst du sicher alle Quizfragen richtig beantworten. Sieh’ dir die Codebeispiele an, und entscheide, ob das fragliche Objekt eine Array oder eine ArrayList ist! Bereit?
zahlen[4] = 42;
Ist zahlen
hier ein Array oder eine ArrayList?
Das ist richtig!
Das stimmt leider nicht. Die eckigen Klammern hätten bei dir die Array-Glocken klingeln lassen müssen…
schiffe.add(new Schiff());
Ist schiffe
hier ein Array oder eine ArrayList?
Leider nein! Die Methode add
hätte dich mißtrauisch machen müssen…
Gut!
int len = zahlen.size();
Ist zahlen
hier ein Array oder eine ArrayList?
Leider nicht! size()
ist eine Methode, die von einer ArrayList zur Verfügung gestellt wird, nicht von einem Array.
Sehr schön!
int sum = 0;
for (int i=0; i<zahlen.length; i++) {
sum += zahlen[i];
}
Ist zahlen
hier ein Array oder eine ArrayList?
Gut gemacht!
Leider nicht! Eine ArrayList hat kein öffentliches Attribut length
, und über [i]
ließe sich auch nicht auf die Elemente zugreifen.
Neue Vokabeln in dieser Lektion
Schreibe den entsprechenden Code auf, und überprüfe, ob du richtig liegst!
Erzeuge ein Ganzzahl-Array hausnummern, das 50 Nummern fasst.
int[] hausnummern = new int[50];
Erzeuge ein String-Array strassen mit den Elementen Parkstraße und Schlossallee.
String[] strassen = {"Parkstraße", "Schlossallee"};
Iteriere mit einer herkömmlichen for-Wiederholung über das Häuser-Array häuser, und rufe an jedem Haus die Methode klingelstreich
auf.
for (int i=0; i<häuser.length; i++) {
häuser[i].klingelstreich();
}
Weise dem 3. Element des Arrays fahrradständer die Fahrrad-Variable flosRad zu.
fahrradständer[2] = flosRad;