3.9 Arrays 

Ein Array (auch Feld oder Reihung genannt) ist ein spezieller Datentyp, der mehrere Werte zu einer Einheit zusammenfasst. Er ist mit einem Setzkasten vergleichbar, in dem die Plätze durchnummeriert sind. Angesprochen werden die Elemente über einen ganzzahligen Index. Jeder Platz (etwa für Schlümpfe) nimmt immer Werte des gleichen Typs auf (nur Schlümpfe und keine Pokemons). Normalerweise liegen die Plätze eines Arrays (seine Elemente) im Speicher hintereinander, doch ist dies ein für Programmierer nicht sichtbares Implementierungsdetail der virtuellen Maschine.
Jedes Array beinhaltet Werte nur eines bestimmten Datentyps bzw. Grundtyps. Dies können sein:
- Elementare Datentypen wie int, byte, long und so weiter
- Referenztypen
- Referenztypen anderer Arrays, um mehrdimensionale Arrays zu realisieren
3.9.1 Deklaration von Arrays 

Eine Array-Variablendeklaration ähnelt einer gewöhnlichen Deklaration, nur dass nach dem Datentyp die Zeichen »[« und »]« gesetzt werden.
int[] chessboard; Point[] points;
Eine Variable wie chessboard hat jetzt den Typ »ist Feld« und »speichert int-Elemente«, also eigentlich zwei Typen.
Klammern [ ] vor oder hinter den Variablennamen
Die eckigen Klammern lassen sich bei der Deklaration einer Array-Variable auch hinter den Namen setzen, doch ganz ohne Unterschied ist die Deklaration nicht. Das zeigt sich spätestens, wenn mehr als eine Variable deklariert wird. Die Klammern können einerseits Teil des Typs sein, andererseits Teil der Variable. Sind sie Teil des Typs, so sind alle deklarierten Variablen ein Feld. Es entspricht demnach
int[] prims, matrix[], threeDimMatrix[][];
der Deklaration:
int prims[], matrix[][], threeDimMatrix[][][];
Hier ist doppelt Vorsicht geboten, denn der eine oder andere wollte vielleicht nur
int []prims,
i;
schreiben, um auszudrücken, dass i eine normale Ganzzahlvariable ist. Stattdessen würde der Compiler jedoch annehmen, dass i ein Feld ist und eine Zuweisung der Art i=2 gnadenlos ablehnen. Es ist aber nicht direkt ersichtlich, wo der Fehler liegt. Damit Irrtümer dieser Art ausgeschlossen werden, sollte nur in jeder Zeile eine Deklaration eines Typs gesetzt werden. Nach reiner Java-Lehre gehören die Klammern jedenfalls hinter den Typbezeichner, so hat es Java-Schöpfer James Gosling gewollt.
3.9.2 Arrays mit Inhalt 

Die bisherigen Deklarationen von Array-Variablen erzeugen noch lange kein Array-Objekt, das die einzelnen Array-Elemente aufnehmen kann. Wenn allerdings die Einträge direkt mit Werten belegt werden sollen, gibt es in Java eine Abkürzung, die ein Array-Objekt anlegt und zugleich mit Werten belegt.
int[] primiMäuschen = { 1, 2, 3, 5, 7, 7 + 4, }; String[] nouns = { "Haus", "Maus", "dog".toUpperCase(), // DOG new java.awt.Point().toString() }; |
In diesem Fall wird ein Feld mit passender Größe angelegt, und die Elemente, die in der Aufzählung genannt sind, werden in das Feld kopiert. Innerhalb der Aufzählung kann abschließend ein Komma stehen, wie die Aufzählung bei primiMäuschen demonstriert.
3.9.3 Die Länge eines Arrays über das Attribut length 

Die Anzahl der Elemente, die ein Array aufnehmen kann, wird Größe beziehungsweise Länge genannt und ist für jedes Array-Objekt in der frei zugänglichen Objektvariable length gespeichert. length ist eine public final int-Variable, deren Wert entweder positiv oder null ist. Die Größe lässt sich später nicht mehr ändern.
int[] primes = { 1, 2, 3, 5, 7, 7 + 4, }; System.out.println( primes.length ); // 6 |
Feldlängen sind final
Das Attribut length eines Felds ist nicht nur öffentlich (public) und vom Typ int, sondern natürlich auch final. Schreibzugriffe sind nicht gestattet. (Was sollten sie bewirken? Eine dynamische Vergrößerung des Felds?) Ein Schreibzugriff führt zu einem Übersetzungsfehler.
3.9.4 Zugriff auf die Elemente über den Index 

Der Zugriff auf die Elemente eines Felds erfolgt mithilfe der eckigen Klammern [], die hinter die Referenz an das Array-Objekt gesetzt werden. In Java beginnt ein Array beim Index 0 (und nicht bei einer frei wählbaren Untergrenze wie in PASCAL). Da die Elemente eines Arrays ab 0 nummeriert werden, ist der letzte gültige Index um 1 kleiner als die Länge des Felds. Bei einem Array a der Länge n ist der gültige Bereich somit a[0] bis a[n – 1].
char[] name = { 'C', 'h', 'r', 'i', 's' }; char first = name[ 0 ]; // C char last = name[ name.length – 1 ]; // s |
Da der Zugriff auf die Variablen über einen Index erfolgt, werden diese Variablen auch indexierte Variablen genannt.
Strings sind keine Arrays
Ein Array von char-Zeichen ist mit keinem String vergleichbar. Die Klasse String bietet jedoch einen Konstruktor an, sodass aus einem Feld mit Zeichen ein String-Objekt erzeugt werden kann. Alle Zeichen des Felds werden kopiert, sodass anschließend Feld und String keine Verbindung mehr besitzen. Dies bedeutet: Wenn sich das Feld ändert, ändert sich der String nicht automatisch mit. Das kann er auch nicht, da Strings unveränderlich sind.
char[] vowels = "aeiouäöü".toCharArray(); |
Über den Typ des Index
Innerhalb der eckigen Klammern steht ein positiver Ganzzahl-Ausdruck, der sich zur Laufzeit berechnen lassen muss. long-Werte, boolean, Gleitkommazahlen oder Referenzen sind nicht möglich; durch int verbleiben aber mehr als zwei Milliarden Elemente. Bei Gleitkommazahlen bliebe die Frage nach der Zugriffstechnik. Hier müssten wir den Wert auf ein Intervall herunterrechnen.
double f = 0.001; // im Intervall von 0 bis 1 System.out.println( a[ (int)(f * 1000) ] ); Rundungsfehler berücksichtigen diese Zeilen natürlich nicht. |
3.9.5 Array-Objekte erzeugen 

Ein Array muss mit dem new-Operator unter Angabe einer festen Größe erzeugt werden. Das Anlegen der Variablen alleine erzeugt noch kein Feld mit einer bestimmten Länge. In Java ist das Anlegen des Felds genauso dynamisch wie die Objekterzeugung. Dies drückt auch der new-Operator aus. Die Länge des Felds wird in eckigen Klammern angegeben. Hier kann ein beliebiger Integer-Wert stehen, auch eine Variable. Selbst 0 ist möglich.
int[] arrayOfInts;
arrayOfInts = new int[ 100 ]; Die Felder mit den primitiven Werten sind mit 0, 0.0 oder false und bei Verweisen mit null initialisiert. |
Die Deklaration ist auch zusammen mit der Zuweisung möglich.
double[] a = new double[ 10 ]; // dann gilt für die Indexwerte 0 <= x <= 9 for ( int i = 0; i < 10; i++ ) a[ i ] = 2 * i; |
Dass Arrays Objekte sind, zeigen einige Indizien:
- Eine spezielle Form des new-Operators erzeugt ein Exemplar der Array-Klasse; new erinnert uns immer daran, dass ein Objekt zur Laufzeit aufgebaut wird.
- Auf dem Array-Objekt sind Methoden – wie clone() und alles, was java.lang.Object hat – definiert, und ein Array-Objekt kennt das Attribut length.
- Die Operatoren == und != haben ihre Objekt-Bedeutung: sie vergleichen lediglich, ob zwei Variablen auf das gleiche Array-Objekt verweisen, aber auf keinen Fall die Inhalte der Arrays (das kann aber Arrays.equals()).
Der Zugriff auf die Array-Elemente über die eckigen Klammern [] lässt sich als versteckter Aufruf über geheime Methoden wie array.get(index) verstehen. Der []-Operator wird bei anderen Objekten nicht angeboten.
int hundredElements[ 100 ]; // Compilerfehler Das ist in Java nicht erlaubt und führt zu einem Compilerfehler. |
Der Index vom Typ char ist auch ein int
Der Index eines Felds muss von einem Typ sein, der ohne Verlust in int konvertierbar ist. Dazu gehören byte, short und char. Günstig ist ein Index vom Typ char, zum Beispiel als Laufvariable, wenn Felder von Zeichenketten generiert werden.
char[] alphabet = new char[ 'z' – 'a' + 1 ]; // 'a' entspricht 97 und 'z' 122 for ( char c = 'a'; c <= 'z'; c++ ) alphabet[ c – 'a' ] = c; // alphabet[0]='a', alphabet[1]='b', usw.
Um aus dem Alphabet ein String-Objekt zu erzeugen, nutzen wir den String(char[])-Konstruktor: new String(alphabet);
private static final char[] hexchars = "0123456789ABCDEF".toCharArray(); public static String charToUnicodeEscape( char c ) { char[] chars = { '\\', 'u', hexchars[c >> 12 & 0xf], hexchars[c >> 8 & 0xf], |
hexchars[c >> 4 & 0xf], hexchars[c & 0xf] }; return new String( chars ); } Der Aufruf System.out.println(charToUnicodeEscape('a')); ergibt die Ausgabe »\u0061«, was der gewünschten Unicode-Zeichenfolge für das Zeichen »a« entspricht. |
Genau genommen haben wir es auch hier mit Indexwerten vom Typ int zu tun, weil mit den char-Werten vorher noch gerechnet wird.
Obwohl es Hexzeichen-Felder schon in anderen Klassen gibt (etwa in Properties), sind diese oft privat. Wenn wir ein eigenes Array in der Klasse definieren, hat dies den zusätzlichen Vorteil, dass keine eventuell unerwünschten Abhängigkeiten von anderen Klassen entstehen.
3.9.6 Fehler bei Arrays 

Beim Zugriff auf ein Array-Element können Fehler auftreten. Zunächst einmal kann das Array-Objekt fehlen, sodass die Referenzierung fehlschlägt. Etwa im folgenden Fall, bei dem der Compiler den Fehler nicht bemerkt: [Obwohl er sich bei nicht initialisierten lokalen Variablen auch beschwert. ]
class NullArray { int[] array; // array ist eine Objektvariable, keine lokale Variable! // Referenz-Objektvariablen werden mit null vorbelegt. void foo() { array[ 1 ] = 1; } }
Die Strafe ist eine NullPointerException.
Der zweite und dritte Fehler liegt im Index begründet. Dieser könnte negativ sein oder über der maximalen Länge liegen. Jeder Zugriff auf das Feld wird zur Laufzeit getestet. Auch bei Operationen, die für den Compiler entscheidbar wären, wird dieser Weg eingeschlagen, etwa bei den folgenden Zeilen:
int[] array = new int[ 100 ]; array[ –10 ] = 1; array[ 100 ] = 1;
Hier könnte der Compiler theoretisch Alarm schlagen, was aber kaum ein Compiler bisher tut, denn der Zugriff auf Elemente mit einem ungültigen Index ist syntaktisch und statisch semantisch völlig in Ordnung.
Ist der Index negativ [Ganz anders verhalten sich da Python oder Perl. Dort wird ein negativer Index dazu verwendet, ein Feldelement relativ zum letzten Array-Eintrag anzusprechen. Und auch bei C ist ein negativer Index durchaus möglich und praktisch. ] oder zu groß, dann hagelt es eine IndexOutOfBoundsException. Wird diese nicht abgefangen, bricht das Laufzeitsystem das Programm mit einer Fehlermeldung ab. Dass die Feldgrenzen überprüft werden, ist Teil von Javas Sicherheit und lässt sich nicht abstellen. Es ist aber heute kein großes Performance-Problem mehr, da die Laufzeitumgebung nicht jeden Index prüfen muss, um sicherzustellen, dass ein Block mit Feldzugriff korrekt ist.
Spielerei: Index und das Inkrement
Wir haben beim Inkrement schon ein Phänomen wie i = i++ betrachtet. Ebenso ist auch die Anweisung bei einem Feldzugriff zu behandeln.
array[ i ] = i++;
Bei der Position array[i] wird i gesichert und anschließend die Zuweisung vorgenommen. Wenn wir eine Schleife darum konstruieren, erweitern wir dies zu einer Initialisierung:
int[] array = new int[ 4 ]; int i = 0; while ( i < array.length ) array[ i ] = i++;
Die Ausgabe ergibt 0, 1, 2 und 3. Von der Anwendung ist wegen mangelnder Übersicht abzuraten.
3.9.7 Vorinitialisierte Arrays 

Wenn wir in Java ein Array-Objekt erzeugen und gleich mit Werten initialisieren wollen, dann schreiben wir etwa:
int[] primes = { 2, 5, 7, 11, 13 };
Sollen die Feldinhalte erst nach der Variablendeklaration initialisiert oder das Feld auch ohne Variable genutzt werden, so erlaubt Java dies nicht, und ein Versuch wie der folgende schlägt fehl:
int[] primes; primes = { 2, 5, 7, 11, 13 }; // Array constants can only // be used in initializers avg( { 1.23, 4.94, 9.33, 3.91, 6.34 } ); // Ebenfalls Compilerfehler
Zur Lösung gibt es zwei Ansätze. Der erste ist die Einführung einer neuen Variable:
int[] primes; int[] tmpprimes = { 2, 5, 7, 11, 13 }; primi = tmpprimes;
Dann gibt es eine Variante des new-Operators, der durch ein Paar eckiger Klammern erweitert wird. Es folgen in geschweiften Klammern die Initialwerte des Arrays. Die Größe des Arrays entspricht genau der Anzahl der Werte. Für die oberen Beispiele ergibt sich folgende Schreibweise:
int[] primes; primes = new int[]{ 2, 5, 7, 11, 13 }; avg( new double[]{ 1.23, 4.94, 9.33, 3.91, 6.34 } );
Da, wie im zweiten Beispiel, ein initialisiertes Feld mit Werten gleich an die Funktion übergeben und keine zusätzliche Variable benutzt wird, heißt diese Art der Arrays »anonyme Arrays«. Eigentlich gibt es auch sonst anonyme Arrays, wie new int[2000].length zeigt, doch wird in diesem Fall das Feld nicht mit Werten initialisiert.
3.9.8 Die erweiterte for-Schleife 

for-Schleifen laufen oft Felder oder Datenstrukturen ab. Nehmen wir als Beispiel einige Zeilen, die von einem Feld array mit Fließkommazahlen den Mittelwert berechnen. Das Feld muss mindestens ein Element besitzen, sonst gibt es bei der Division eine Ausnahme.
double sum = 0; for ( int i = 0; i < array.length; i++ ) sum += array[ i ]; double arg = sum / array.length;
Die Schleifenvariable i hat lediglich als Index ihre Berechtigung; nur damit lässt sich das Element an einer bestimmten Stelle im Feld ansprechen.
Weil das komplette Durchlaufen von Feldern häufig ist, haben die Entwickler bei Sun seit Java 5 eine Abkürzung für solche Iterationen in die Sprache eingeführt:
for ( Typ Bezeichner : Feld ) ...
Die erweiterte Form der for-Schleife löst sich vom Index und erfragt jedes Element des Feldes. Das lässt sich als Durchlauf einer Menge vorstellen, denn der Doppelpunkt liest sich als »in«. Rechts vom Doppelpunkt steht immer ein Feld oder, wie wir später sehen werden, etwas vom Typ Iterable, wie eine Datenstruktur. Links wird eine lokale Variable deklariert, die später beim Ablauf jedes Element der Sammlung annehmen wird.
Die Berechnung des Durchschnitts lässt sich nun umschreiben. Die Funktion avg() soll den Mittelwert der Elemente eines Feldes bestimmen. Eine Ausnahme zeigt an, ob der Feldverweis null ist oder das Feld keine Elemente enthält.
Listing 3.9 Avg.java, avg()
static double avg( double[] array )
{
if ( array == null || array.length == 0 )
throw new IllegalArgumentException( "Illegal array!" );
double sum = 0;
for ( double n : array )
sum += n;
return sum / array.length;
}
Zu lesen ist die for-Zeile demnach als »Für jedes Element n vom Typ double in array tue ...«. Eine Variable für den Schleifenindex ist nicht mehr nötig.
Listing 3.10 FirstPrimes.java, main() for ( int prime : new int[]{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41 } ) System.out.println( prime ); |
Umsetzung und Einschränkung
Intern setzt der Compiler diese erweiterte for-Schleife ganz klassisch um, sodass der Bytecode unter beiden Varianten gleich ist. Nachteile der Variante sind jedoch:
- Das erweiterte for läuft immer das ganze Feld ab. Anfang- und Ende-Index können nicht ausdrücklich gesetzt werden.
- Die Ordnung ist immer von vorne nach hinten.
- Der Index ist nicht sichtbar.
- Die Schleife liefert ein Element, kann aber nicht in das Feld schreiben.
Abbrechen lässt sich Schleifen mit einem break. Bestehen andere Anforderungen, kann weiterhin nur eine klassische for-Schleife helfen.
3.9.9 Arrays mit nicht-primitiven Elementen 

Der Datentyp der Array-Elemente muss nicht zwingend ein primitiver sein. Auch ein Array von Objektreferenzen kann deklariert werden. Dieses Array besteht dann nur aus Referenzen auf die eigentlichen Objekte, die in dem Array abgelegt werden sollen. Die Größe des Arrays im Speicher errechnet sich demnach aus der Länge des Felds, multipliziert mit dem Speicherbedarf einer Referenz. Nur das Array-Objekt selbst wird angelegt, nicht aber die Objekte, die das Array aufnehmen soll. Dies lässt sich einfach damit begründen, dass der Compiler auch gar nicht wüsste, welchen Konstruktor er aufrufen sollte.
Point[] points = new Point[ 5 ]; Hier wird Platz für fünf Verweise auf Punkt-Objekte schaffen, aber kein einziges Point-Objekt angelegt. Später würde das Feld etwa mit points[0] = new Point() gefüllt. Standardmäßig werden die Array-Elemente mit der null-Referenz initialisiert. |
Fünf Punkte sollen angelegt und mit willkürlichen Werten gefüllt werden. Die Zufallszahlen erzeugt die mathematische Funktion Math.random(). Da die Funktion jedoch Fließkommazahlen zwischen 0 und 1 liefert, werden die Zahlen zunächst durch Multiplikation frisiert und dann abgeschnitten.
Point[] points = new Point[ 5 ]; for ( int i = 0; i < points.length; i++ ) points[i] = new Point( (int)(Math.random() * 100), (int)(Math.random() * 100) ); for ( int i = 0; i < points.length; i++ ) System.out.println( points[ i ] );
Die Ausgabe erzeugt zum Beispiel Folgendes:
java.awt.Point[x=59,y=77] java.awt.Point[x=47,y=86] java.awt.Point[x=18,y=71] java.awt.Point[x=55,y=97] java.awt.Point[x=12,y=70]
3.9.10 Mehrdimensionale Arrays 

Java realisiert mehrdimensionale Arrays durch Arrays von Arrays. Sie können etwa für die Darstellung von mathematischen Matrizen oder Rasterbildern Verwendung finden.
int[][] A3x2 = {{1, 2}, {2, 3}, {3, 4}}; int[][] B = {{1, 2}, {2, 3, 4}, {5, 6}}; Der zweite Fall lässt erkennen, dass das Feld nicht unbedingt rechteckig sein muss. |
Die folgende Zeile deklariert ein zweidimensionales Feld mit dem Platz für 32 Zellen, angeordnet in 4 Zeilen und 8 Spalten:
int[][] A = new int[ 4 ][ 8 ];
Zwei alternative Deklarationen sind:
int A[][] = new int[ 4 ][ 8 ]; // Der Typ von A ist ein zweidimensionales Array int[] A[] = new int[ 4 ][ 8 ]; // mit dem Elementtyp int
Einzelne Elemente spricht der Ausdruck A[i][j] an. [Die in PASCAL übliche Notation A[i,j] wird in Java nicht unterstützt. Das wäre im Prinzip möglich, da Java im Gegensatz zu C(++) den Kommaoperator nur in for-Schleifen zulässt. In C(++) brachte die Schreibweise hübsche Fehler hervor, die zur Übersetzungszeit nicht angezeigt wurden. ] Der Zugriff erfolgt mit so vielen Klammerpaaren, wie die Dimension des Arrays angibt. Obwohl mehrdimensionale Arrays im Prinzip Arrays mit Arrays als Elementen sind, lassen sie sich leicht deklarieren.
a[0][0] a[0][1] a[0][2] a[0][3] a[0][4] a[0][5] ... a[1][0] a[1][1] a[1][2] a[1][3] a[1][4] a[1][5] a[2][0] a[2][1] a[2][2] a[2][3] a[2][4] a[2][5] ... |
Nicht-rechteckige Felder
Da in Java mehrdimensionale Arrays als Arrays von Arrays implementiert sind, müssen diese nicht zwingend rechteckig sein. Jede Zeile im Feld kann eine eigene Größe haben.
int[][] m = new int[ 3 ][]; for ( int i = 0; i < 3; i++ ) m[ i ] = new int[ i + 1 ]; |
Initialisierung von mehrdimensionalen Feldern
Wenn wir ein mehrdimensionales Feld deklarieren, erzeugen versteckte Schleifen automatisch die inneren Felder. Im Vergleich von
int[][] m = new int[ 3 ][ 4 ]; int[][] m = new int[ 3 ][];
erzeugt die Laufzeitumgebung die passenden Unterfelder automatisch. Dies ist im zweiten Fall nicht so. Hier müssen wir selbst die Unterfelder initialisieren, bevor wir auf die Elemente zugreifen.
for ( int i = 0; i < m.length; i++ ) m[ i ] = new int[ 4 ];
Das Pascal’sche Dreieck
Das folgende Beispiel zeigt eine weitere Anwendung von nichtrechteckigen Arrays, in der das Pascal’sche Dreieck nachgebildet wird. Das Dreieck ist so aufgebaut, dass die Elemente unter einer Zahl genau die Summe der beiden direkt darüber stehenden Zahlen bilden. Die Ränder sind mit Einsen belegt.
1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 1 6 15 20 15 6 1
Abbildung 3.4 Das Pascal’sche Dreieck
In der Implementierung wird zu jeder Ebene dynamisch ein Feld mit der passenden Länge angefordert. Die Ausgabe tätigt printf(), da wir auf diese Weise ein führendes Leerzeichen bekommen, wenn die Zahl nur aus einer Ziffer besteht.
Listing 3.11 PascalsTriangle.java
class PascalsTriangle { public static void main( String[] args ) { int[][] triangle = new int[7][]; for ( int row = 0; row < triangle.length; row++ ) // All rows { triangle[row] = new int[row + 1]; // New column of len i+1 for ( int col = 0; col <= row; col++ ) // Columns { if ( (col == 0) || (col == row) ) triangle[row][col] = 1; // Borders on 1 else triangle[row][col] = triangle[row – 1][col – 1] + triangle[row – 1] [col]; System.out.printf( "%2d ", triangle[row][col] ); } System.out.println(); } } }
Andere Anwendungen
Auf diese Art und Weise ist die Verwaltung von symmetrischen Matrizen einfach, da eine solche Matrix symmetrisch zur Diagonalen gleiche Elemente enthält. Daher kann entweder die obere oder die untere Dreiecksmatrix entfallen. Besonders nützlich ist der Einsatz dieser effizienten Speicherform für Adjazenzmatrizen [Eine Adjazenzmatrix stellt eine einfache Art dar, Graphen zu speichern. Sie besteht aus einem zweidimensionalen Array, das die Informationen über vorhandene Kanten im (gerichteten) Graphen enthält. Existiert eine Kante von einem Knoten zum anderen, so befindet sich in der Zelle ein Eintrag: entweder true/false für »Ja, die beiden sind verbunden« oder ein Ganzzahlwert für eine Gewichtung (Kantengewicht). ] bei ungerichteten Graphen.
3.9.11 Die Wahrheit über die Array-Initialisierung 

So schön die kompakte Initialisierung der Feldelemente ist, so laufzeit- und speicherintensiv ist sie auch. Da Java eine dynamische Sprache ist, passt das Konzept der Array-Initialisierung nicht ganz in das Bild. Daher wird die Initialisierung auch erst zur Laufzeit durchgeführt. Bleiben wir bei unseren Primzahlen:
int[] primes = { 1, 2, 3, 5, 7, 7 + 4 };
wird vom Java-Compiler umgeformt und analog zu Folgendem behandelt:
int[] primes = new int[ 6 ]; primes[ 0 ] = 1; primes[ 1 ] = 2; primes[ 2 ] = 3; primes[ 3 ] = 5; primes[ 4 ] = 7; primes[ 5 ] = 11;
Erst nach kurzem Überlegen wird das erschreckende Ausmaß sichtbar. Zunächst ist es der Speicherbedarf für die Methoden. Ist das Feld primes beispielsweise in einer lokalen Methode untergebracht, kostet die Zuweisung sehr viel Laufzeit, da wir viele Zugriffe haben, die auch alle schön durch die Index-Überprüfung gesichert sind. Da zudem der Bytecode für eine einzelne Methode wegen diverser Beschränkungen in der JVM nur beschränkt lang sein darf, kann dieser Platz für richtig große Arrays schnell erschöpft sein. Daher ist davon abzuraten, etwa Bilder oder große Tabellen im Programmcode zu speichern. Unter C war es populär, ein Programm einzusetzen, das eine Datei in eine Folge von Array-Deklarationen verwandelte. Ist dies in Java wirklich nötig, sollten wir Folgendes in Betracht ziehen:
- Wir verwenden ein statisches Feld (eine Klassenvariable), sodass das Array nur einmal während des Programmlaufs initialisiert werden muss.
- Sind die Werte im Byte-Bereich, können wir diese in einen String konvertieren und später den String in ein Feld umwandeln. (Das ist eine sehr clevere Methode, um Binärdaten einfach unterzubringen.)
3.9.12 Mehrere Rückgabewerte 

Wenn wir in Java Funktionen schreiben, dann haben sie über return höchstens einen Rückgabewert. Wollen wir aber mehr als einen Wert zurückgeben, müssen wir eine andere Lösung suchen. Zwei Ideen lassen sich verwirklichen:
- Behälter wie Arrays oder andere Sammlungen fassen Werte zusammen und liefert sie als Rückgabe.
- Spezielle Behälter werden übergeben, in der die Funktion Rückgabewerte platziert; eine return-Anweisung ist nicht mehr nötig.
Betrachten wir eine Methode, die für zwei Zahlen die Summe und das Produkt als Array liefert.
Listing 3.12 MultipleReturnValues.java
public class MultipleReturnValues { static int[] productAndSum( int a, int b ) { return new int[]{ a * b, a + b }; } public static void main( String[] args ) { System.out.println( productAndSum(9, 3)[ 1 ] ); } }
static int[] productAndSum( int a, int b ) alternativ zu schreiben static int productAndSum( int a, int b )[] |
Die zweite Variante besteht darin, einen Behälter zu übergeben, in den die Funktion ihre Rückgaben legt. Java bietet zur Aufnahme von Verweisen eine Klasse javax.xml.ws.Holder, die einen Wert in der öffentlichen Variablen value aufnimmt. Das sieht dann so aus:
Listing 3.13 MultipleReturnValuesWithHolder.java, Ausschnitte
static void productAndSum( int a, int b, Holder sum, Holder product ) { sum.value = a + b; product.value = a * b; }
Der Aufrufer muss die Behälter aufbauen und übergeben:
Holder sum = new Holder(); Holder product = new Holder(); productAndSum( 9, 3, sum, product ); System.out.printf( "sum=%s product=%s", sum.value, product.value ); // sum=12 product=27
Die Klasse Holder ist generisch deklariert, was aber an dieser Stelle irrelevant ist.
3.9.13 Methode mit variabler Argumentanzahl (Vararg) 

Bei vielen Methoden ist es klar, wie viele Argumente sie haben; eine Sinus-Funktion bekommt ohnehin nur ein Argument. Es gibt jedoch Funktionen, wo die Zahl mehr oder weniger frei ist, etwa bei der Funktion max(). Die Klasse java.lang.Math sieht eine max()-Funktion mit zwei Argumenten vor, doch grundsätzlich könnte die Funktion auch ein Feld entgegennehmen und von diesen Elementen das Maximum bilden. Java 5 sieht eine weitere Möglichkeit vor: Methoden mit variabler Argumentanzahl, auch Varargs genannt.
Eine Funktion mit variabler Argumentanzahl nutzt die Ellipse (»...«) zur Verdeutlichung, dass eine beliebige Anzahl Argumente angegeben werden dürfen. Der Typ fällt dabei aber nicht unter den Tisch; er wird ebenfalls angegeben:
int max( int... array )
{
}
Die Funktion max() behandelt array wie ein Feld. Da wir Argumente vom Typ int fordern, ist array vom Typ int[] und kann so zum Beispiel mit dem erweiterten for durchlaufen werden:
for ( int e : array ) ...
Werden variable Argumentlisten in der Signatur definiert, so dürfen sie nur den letzten Parameter bilden; andernfalls könnte der Compiler bei den Parametern nicht unbedingt zuordnen, was nun ein Vararg und was schon der nächste gefüllte Parameter ist.
Listing 3.14 MaxVarArgs.java
public class MaxVarArgs { public static int max( int... array ) { if ( array == null || array.length == 0 ) throw new IllegalArgumentException( "Illegal array!" ); int currentMax = Integer.MIN_VALUE; for ( int e : array ) if ( e > currentMax ) currentMax = e; return currentMax; } public static void main( String[] args ) { System.out.println( max(1, 2, 9, 3) ); // 9 } }
Der Nutzer kann jetzt die Funktion aufrufen, ohne ein Feld für die Argumente explizit zu definieren. Er bekommt auch gar nicht mit, dass der Compiler im Hintergrund ein Feld mit vier Elementen angelegt hat. So übergibt der Compiler:
System.out.println( max( new int[] { 1, 2, 9, 3 } ) );
An der Schreibweise lässt sich gut ablesen, dass wir ein Feld auch von Hand übergeben können:
int[] feld = { 1, 2, 9, 3 }; System.out.println( max(feld) );
3.9.14 Klonen kann sich lohnen – Arrays vermehren 

Wollen wir eine Kopie eines Arrays mit gleicher Größe und gleichem Elementtyp schaffen, so nutzen wir dazu die Objektmethode clone(). [Das ist gültig, da Arrays die Schnittstelle Cloneable implementieren. ] Sie klont – in unserem Fall: kopiert – die Elemente des Array-Objekts in ein neues. Im Fall von geklonten Objekt-Feldern ist wichtig zu verstehen, dass die Kopie flach ist. Die Verweise aus dem ersten Feld kopiert clone() in das neue, klont aber die Objekte selbst nicht. Bei mehrdimensionalen Arrays wird also nur die erste Dimension kopiert, Unter-Arrays werden somit gemeinsam genutzt.
Listing 3.15 CloneDemo.java, main()
int[] sourceArray = new int[ 6 ]; sourceArray[ 0 ] = 4711; int[] targetArray = sourceArray.clone(); System.out.println( targetArray.length ); // 6 System.out.println( targetArray[ 0 ] ); // 4711 Point[] pointArray1 = { new Point(1, 2), new Point(2, 3) }; Point[] pointArray2 = pointArray1.clone(); System.out.println( pointArray1[ 0 ] == pointArray2[ 0 ] ); // true
Die letzte Zeile zeigt anschaulich, dass die beiden Felder dasselbe Point-Objekt referenzieren; die Kopie ist flach, aber nicht tief.
3.9.15 Feldinhalte kopieren 

Eine weitere nützliche Funktion ist die statische Funktion System.arraycopy(). Sie kann auf zwei Arten arbeiten:
- Auf zwei schon existierenden Feldern. Ein Teil eines Feldes wird in ein anderes Feld kopiert. arraycopy() eignet sich dazu, sich vergrößernde Felder zu implementieren, indem zunächst ein neues größeres Feld angelegt wird und anschließend die alten Feldinhalte in das neue Feld kopiert werden.
- Auf dem gleichen Feld. So lässt sich die Methode dazu verwenden, Elemente eines Felds um bestimmte Positionen zu verschieben. Die Bereiche können sich durchaus überlappen.
final class java.lang.System |
- static void arraycopy( Object src, int srcPos, Object dest, int destPos, int length )
-
- Kopiert length viele Einträge des Arrays src ab der Position srcPos in ein Array dest ab der Stelle destPos. Der Typ des Feldes ist egal, es muss nur in beiden Fällen der gleiche Typ sein. Die Methode ist zumindest für große Felder schneller als eine eigene Kopierschleife.
Abbildung 3.5 Kopieren der Elemente von einem Feld in ein anderes
System.arraycopy( f, 1, f, 0, f.length – 1 ); // links System.arraycopy( f, 0, f, 1, f.length – 1 ); // rechts Hier bleibt jedoch ein Element doppelt! |
3.9.16 Die Klasse Arrays zum Vergleichen, Füllen und Suchen 

Die Klasse java.util.Arrays definiert nützliche Funktionen im Umgang mit Arrays. So bietet sie statische Funktionen zum Vergleichen, Sortieren und Füllen von Feldern sowie zur binären Suche.
Felder vergleichen mit Arrays.equals() und Arrays.deepEquals()
Die Funktion Arrays.equals() vergleicht, ob zwei Felder die gleichen Inhalte besitzen. Dazu ist die überladene Funktion für alle wichtigen Typen definiert. Wenn ja, ist die Rückgabe der Funktion true, sonst false. Natürlich müssen beide Arrays schon die gleiche Anzahl von Elementen besitzen, sonst ist der Test sofort vorbei und das Ergebnis false. Im Fall von Objektfeldern nutzt Arrays.equals() nicht die Identitätsprüfung per ==, sondern die Gleichheit per equals().
int[] array = { 1, 2, 3, 4 }; int[] clone = array.clone(); System.out.println( Arrays.equals( array, clone ) ); // true
Ein Vergleich von Teilfeldern ist leider auch nach mehr als zehn Jahren Java-Bibliothek nicht einfach vorgesehen.
Bei unterreferenzierten Feldern betrachtet Arrays.equals() das innere Feld als einen Objektverweis und vergleicht es auch mit equals() – was jedoch bedeutet, dass nicht identische, aber mit gleichen Elementen referenzierte innere Felder als ungleich betrachtet werden. Die Funktion deepEquals() bezieht auch unterreferenzierte Felder in den Vergleich mit ein. Ein Beispiel verdeutlicht den Unterschied zwischen equals() und deepEquals():
Listing 3.16 ArrayEqualsDemo.java, main()
int[][] a1 = { { 0, 1 }, { 1, 0 } }; int[][] a2 = { { 0, 1 }, { 1, 0 } }; System.out.println( Arrays.equals( a1, a2 ) ); // false System.out.println( Arrays.deepEquals( a1, a2 ) ); // true System.out.println( a1[0] ); // z.B. [I@10b62c9 System.out.println( a2[0] ); // z.B. [I@82ba41
Den Grund für das unterschiedliche Verhalten zeigen die beiden letzten Konsolenausgaben: Die von a1 und a2 unterreferenzierten Felder enthalten die gleichen Elemente, sind aber zwei unterschiedliche Objekte, also nicht identisch. deepEquals() vergleicht auch eindimensionale Felder:
Object[] b1 = { "1", "2", "3" }; Object[] b2 = { "1", "2", "3" }; System.out.println( Arrays.deepEquals( b1, b2 ) ); // true
class java.util.Arrays |
- static boolean equals( boolean[] a, boolean[] a2 )
- static boolean equals( byte[] a, byte[] a2 )
- static boolean equals( char[] a, char[] a2 )
- static boolean equals( double[] a, double[] a2 )
- static boolean equals( float[] a, float[] a2 )
- static boolean equals( int[] a, int[] a2 )
- static boolean equals( long[] a, long[] a2 )
- static boolean equals( short[] a, short[] a2 )
- static boolean equals( Object[] a, Object[] a2 ) Vergleicht zwei Felder und liefert true, wenn die Felder gleich groß und Elemente paarweise gleich sind. Ein Objekt-Feld darf null enthalten; dann gilt für die Gleichheit e1==null ? e2==null : e1.equals(e2).
- static boolean deepEquals( Object[] a1, Object[] a2 ) Liefert true, wenn die beiden Felder ebenso wie alle Unterfelder – rekursiv im Fall von Unter-Objekt-Feldern – gleich sind.
Füllen von Feldern
Arrays.fill() füllt ein Feld mit einem festen Wert. Der Start-Endbereich lässt sich optional angeben.
class java.util.Arrays |
- static void fill( boolean[] a, boolean val )
- static void fill( boolean[] a, int fromIndex, int toIndex, boolean val )
- static void fill( byte[] a, byte val )
- static void fill( byte[] a, int fromIndex, int toIndex, byte val )
- static void fill( char[] a, char val )
- static void fill( char[] a, int fromIndex, int toIndex, char val )
- static void fill( double[] a, double val )
- static void fill( double[] a, int fromIndex, int toIndex, double val )
- static void fill( float[] a, float val )
- static void fill( float[] a, int fromIndex, int toIndex, float val )
- static void fill( int[] a, int val )
- static void fill( int[] a, int fromIndex, int toIndex, int val )
- static void fill( long[] a, int fromIndex, int toIndex, long val )
- static void fill( long[] a, long val )
- static void fill( Object[] a, int fromIndex, int toIndex, Object val )
- static void fill( Object[] a, Object val )
- static void fill( short[] a, int fromIndex, int toIndex, short val )
- static void fill( short[] a, short val ) Setzt das Element val in das Feld. Beim Bereich ist fromIndex inklusiv und toIndex exklusiv.
Feldabschnitte klonen
Seit Java 6 bietet die Klasse Arrays eine Reihe von copyOf()- bzw. copyOfRange()-Methoden, die gegenüber clone() den Vorteil haben, dass sie auch Bereichsangaben erlauben und das neue Feld größer machen können; im letzten Fall füllen die Methoden das Feld je nach Typ mit null, false oder 0.
Listing 3.17 ArraysCopyOfDemo.java, main()
String[] snow = { "Neuschnee", "Altschnee", "Harsch", "Firn" }; String[] snow1 = Arrays.copyOf( snow, 2 ); // [Neuschnee, Altschnee] String[] snow2 = Arrays.copyOf( snow, 5 ); // [Neuschnee, Altschnee, Harsch, Firn, null] String[] snow3 = Arrays.copyOfRange( snow, 2, 4 ); // [Harsch, Firn] String[] snow4 = Arrays.copyOfRange( snow, 2, 5 ); // [Harsch, Firn, null]
class java.util.Arrays |
- static boolean[] copyOf( boolean[] original, int newLength )
- static byte[] copyOf( byte[] original, int newLength )
- static char[] copyOf( char[] original, int newLength )
- static double[] copyOf( double[] original, int newLength )
- static float[] copyOf( float[] original, int newLength )
- static int[] copyOf( int[] original, int newLength )
- static long[] copyOf( long[] original, int newLength )
- static short[] copyOf( short[] original, int newLength )
- static <T> T[] copyOf( T[] original, int newLength )
- static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType )
- static boolean[] copyOfRange( boolean[] original, int from, int to )
- static byte[] copyOfRange( byte[] original, int from, int to )
- static char[] copyOfRange( char[] original, int from, int to )
- static double[] copyOfRange( double[] original, int from, int to )
- static float[] copyOfRange( float[] original, int from, int to )
- static int[] copyOfRange( int[] original, int from, int to )
- static long[] copyOfRange( long[] original, int from, int to )
- static short[] copyOfRange( short[] original, int from, int to )
- static <T> T[] copyOfRange( T[] original, int from, int to )
- static <T,U> T[] copyOfRange( U[] original, int from, int to, Class<? extends T[]> newType ) Erzeugt ein neues Feld mit der gewünschten Größe beziehungsweise dem angegebenen Bereich aus einem existierenden Feld.
Sortieren
Diverse Arrays.sort()-Funktionen ermöglichen das Sortieren von Elementen im Feld. Bei primitiven Elementen (kein boolean) gibt es keine Probleme, da sie eine natürliche Ordnung haben. Im Fall von Objekten müssen sie vergleichbar sein. Das gelingt entweder mit einem extra Comparator, oder die Klassen implementieren die Schnittstelle Comparable. Das Kapitel 12 über Datenstrukturen und Algorithmen beschreibt diese Möglichkeiten präzise.
class java.util.Arrays |
- static void sort( byte[] a )
- static void sort( byte[] a, int fromIndex, int toIndex )
- static void sort( char[] a )
- static void sort( char[] a, int fromIndex, int toIndex )
- static void sort( double[] a )
- static void sort( double[] a, int fromIndex, int toIndex )
- static void sort( float[] a )
- static void sort( float[] a, int fromIndex, int toIndex )
- static void sort( int[] a )
- static void sort( int[] a, int fromIndex, int toIndex )
- static void sort( long[] a )
- static void sort( long[] a, int fromIndex, int toIndex )
- static void sort( short[] a )
- static void sort( short[] a, int fromIndex, int toIndex ) Sortiert die gesamte Liste oder einen Teil. Bei angegebenen Grenzen ist fromIndex wieder inklusiv und toIndex exklusiv. Sind die Grenzen fehlerhaft, löst die Funktion eine IllegalArgumentException (im Fall fromIndex > toIndex) bzw. ArrayIndexOutOfBoundsException (fromIndex < 0 oder toIndex > a.length) aus.
- static void sort( Object[] a )
- static void sort( Object[] a, int fromIndex, int toIndex ) Sortiert ein Feld von Objekten. Die Elemente müssen Comparable implementieren. Bei der Funktion gibt es keinen generischen Typparameter.
- static <T> void sort( T[] a, Comparator<? super T> c )
- static <T> void sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c ) Sortiert ein Feld von Objekten mit gegebenem Comparator.
Das Sortierverfahren ist für primitive Datentypen ein modifizierter Quicksort und für Objekte ein angepasster Mergesort.
String-Repräsentation eines Feldes
Nehmen wir an, wir haben es mit einem Feld von Hundenamen zu tun, das wir auf dem Bildschirm ausgeben wollen.
Listing 3.18 DogArrayToString, main()
String[] dogs = { "Flocky Fluke", "Frizzi Faro", "Fanny Favorit", "Frosty Filius", "Face Flash", "Fame Friscco" };
Soll der Feldinhalt zum Testen auf den Bildschirm gebracht werden, so kommt eine Ausgabe mit System.out.println(hundenamen) nicht infrage, denn toString() ist auf dem Objekttyp Array nicht sinnvoll definiert.
System.out.println( dogs ); // [Ljava.lang.String;@10b62c9
Die Funktion Arrays.toString(array) liefert für unterschiedliche Feldtypen die gewünschte String-Repräsentation des Feldes.
System.out.println( Arrays.toString(dogs) ); // [Flocky Fluke, ...]
Das spart eine for-Schleife, die durch das Feld läuft und auf jedem Element print() aufruft.
class java.util.Arrays |
- static String toString( boolean[] a )
- static String toString( byte[] a )
- static String toString( char[] a )
- static String toString( double[] a )
- static String toString( float[] a )
- static String toString( int[] a )
- static String toString( long[] a )
- static String toString( short[] a )
- static String toString( Object[] a ) Liefert eine String-Repräsentation des Feldes. Im Fall des Objekttyps ruft die Funktion auf jedem Objekt im Feld toString() auf.
- static String deepToString( Object[] a ) Ruft auch auf jedem Unterfeld Arrays.toString() auf und nicht nur toString() auf dem Feld.
Halbierungssuche
Ist das Feld sortiert, lässt sich mit Arrays.binarySearch() eine binäre Suche (Halbierungssuche) durchführen. Ist das Feld nicht sortiert, ist das Ergebnis unvorhersehbar. Findet binarySearch() das Element, ist der Rückgabewert der Index der Fundstelle, andernfalls ist die Rückgabe negativ.
class java.util.Arrays |
- static int binarySearch( byte[] a, byte key )
- static int binarySearch( char[] a, char key )
- static int binarySearch( double[] a, double key )
- static int binarySearch( float[] a, float key )
- static int binarySearch( int[] a, int key )
- static int binarySearch( long[] a, long key )
- static int binarySearch( short[] a, short key ) Sucht mit der Halbierungssuche nach einem Schlüssel.
- static int binarySearch( Object[] a, Object key ) Sucht mit der Halbierungssuche nach key. Die Objekte müssen die Schnittstelle Comparable implementieren; das bedeutet im Allgemeinen, dass die Elemente vom gleichen Typ sein müssen – also nicht Strings und Hüpfburg-Objekte gemischt.
- static <T> int binarySearch( T[] a, T key, Comparator<? super T> c ) Sucht mit der Halbierungssuche ein Element im Objektfeld. Die Vergleiche übernimmt ein spezielles Vergleichsobjekt c.
- static <T> int binarySearch( T[] a, int fromIndex, int toIndex, T key, Comparator<? super T> c ) Schränkt die Binärsuche auf Bereiche ein.
Die API-Dokumentation von binarySearch() ist durch Verwendung der Generics (mehr darüber in Kapitel 6) etwas schwieriger. Wir werden in Kapitel 12 noch einmal auf die Funktion binarySearch() für beliebige Listen zurückkommen und insbesondere die Bedeutung der Schnittstellen Comparator und Comparable in Kapitel 6 genau klären.
Felder zu Listen mit Arrays.asList() – praktisch für die Suche und zum Vergleichen
Ist das Feld unsortiert, funktioniert binarySearch() nicht. Die Klasse Arrays hat für diesen Fall keine Funktion im Angebot – eine eigene Schleife muss her. Es gibt aber noch eine Möglichkeit. Die Funktion Arrays.asList() dekoriert das Array als Liste vom Typ java.util.List, die dann praktische Funktionen wie contains(), equals() oder sublist() anbietet. Mit den Methoden sind Dinge auf Feldern möglich, für die Arrays bisher keine Methoden definierte.
if ( Arrays.asList( args ).contains( "-?" ) ) ... |
Listing 3.19 AsListDemo.java, main() // Index 0 1 2 String[] a = { "Asus", "Elitegroup", "MSI" }; String[] b = { "Elitegroup", "MSI", "Shuttle" }; System.out.println( Arrays.asList( a ).subList( 1, 3 ). equals( Arrays.asList( b ).subList( 0, 2 ) ) ); // true Im Fall von subList() ist der Start-Index inklusiv und der End-Index exklusiv. (Das ist die Standard-Notation von Bereichen in Java, etwa auch bei substring() oder fill().) Somit werden in obigem Beispiel die Einträge 1 bis 2 aus a mit den Einträgen 0 bis 1 aus b ver-glichen. |
class java.util.Arrays |
- static <T> List<T> asList( T... a ) Liefert eine Liste vom Typ T bei einem Feld vom Typ T.
Die Funktion asList() nimmt über das Varargs entweder ein Feld von Objekten (kein primitives Feld!) an oder aufgezählte Elemente. Im Fall der aufgezählten Elemente ist auch kein oder genau ein Element erlaubt, wie folgendes Beispiel zeigt:
System.out.println( Arrays.asList() ); // [] System.out.println( Arrays.asList("Chris") ); // [Chris]
Dass das übergebende Feld kein primitives Feld sein darf, veranschaulicht das folgende Beispiel:
int[] nums = { 1, 2 }; System.out.println( Arrays.asList(nums).toString() ); // [[I@82ba41] System.out.println( Arrays.toString(nums) ); // [1, 2]
Der Grund ist einfach: Arrays.asList() erkennt nums nicht als Feld von Objekten, sondern als genau ein Element einer Aufzählung. So setzt die Funktion das Feld mit Primitiven als ein Element in die Liste, und toString() eines java.util.List-Objekts ruft lediglich auf dem Feld-Objekt toString() auf, was die kryptische Ausgabe zeigt.