3.5 Arrays
Ein Array (auch »Feld« oder »Reihung« genannt) ist ein spezieller Datentyp, der mehrere Werte, die über einen ganzzahligen Index angesprochen werden, zu einer Einheit zusammenfasst. Er ist vergleichbar mit einem Setzkasten, in dem die Plätze durchnummeriert sind. 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 dies ist ein Implementierungsdetail, welches in Java unwichtig ist.
Jedes Array beinhaltet Werte eines bestimmten Datentyps. Dies können sein:
|
Elementare Datentypen wie int, byte, long und so weiter |
|
Referenzen auf Objekte |
|
Referenzen auf andere Arrays, um mehrdimensionale Arrays zu realisieren |
3.5.1 Deklaration von Arrays
Eine Array-Variablendeklaration ähnelt einer gewöhnlichen Deklaration, nur dass nach dem Datentyp oder den Variablen die Zeichen »[« und »]« gesetzt werden müssen. Uns ist es freigestellt, welche Schreibweise wir wählen. Hauptsache, es werden überhaupt Klammern gesetzt. Wie bei der gesamten Programmierung, so sollte auch hier konsistent vorgegangen werden - einmal so, einmal so, behindert die schnelle Wahrnehmung von Programmquelltext.
int schachfeld[];
int [] auchSchach;
Point punkte[];
Die Klammern [ ] vor oder hinter die Variable setzen?
So 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 Variablen. Sind sie Teil des Typs, so sind alle deklarierten Variablen ein Feld. Es entspricht demnach
int[] prims, matrix[], 3dmatrix[][];
der Deklaration
int prims[], matrix[][], 3dmatrix[][][];
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. Ich empfehle daher, die Klammern hinter den Bezeichner und bestenfalls in jede Zeile nur eine Deklaration zu setzen. (Aus diesem Grund ist es auch ungünstig, unter C(++) das Sternchen für den Zeiger direkt an den Datentyp zu packen.1) Das beugt Fehlern vor. Nach reiner Java-Lehre jedenfalls gehören die Klammern hinter den Typbezeichner, so hat es Gosling gewollt.
3.5.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, so gibt es in Java eine Abkürzung, die ein Array-Objekt anlegt und zugleich mit Werten belegt.
Beispiel Wertebelegung eines Felds
int primiMäuschen[] = { 1, 2, 3, 5, 7, 7 + 4, };
String substantive[] = {
"Haus",
|
"Maus",
translator.toGerman( "dog" ), // fiktives Objekt translator
new Point().toString()
};
|
In diesem Fall wird ein Feld mit passender Größe angelegt, und die Elemente werden in das Feld kopiert, das in der Aufzählung genannt ist. Innerhalb der Aufzählung kann als Letztes ein Komma stehen, wie die Aufzählung bei primiMäuschen demonstriert.
Es ist nicht möglich - wie in C(++) - das Feld mit einer bestimmten Größe zu initialisieren, ohne gleichzeitig die Werte aller Elemente aufzuzählen.
Beispiel Folgende Zeile ist falsch und führt zu einem Compilerfehler:
int einhundertElemente[100]; // Compilerfehler
|
Wir müssen diesen Fall über eine explizite Objekterzeugung lösen, wie wir etwas später mit einem new-Operator sehen werden.
Strings sind keine Arrays
Ein Array von Char-Zeichen ist nicht mit einem 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. Das bedeutet, falls sich das Feld ändert, ändert sich der String nicht automatisch mit. Das kann er auch nicht, da Strings unveränderlich sind.
Beispiel Mit der Methode toCharArray() können wir einen String in ein char-Feld konvertieren.
char umlaut[] = "aeiouäöü".toCharArray();
|
3.5.3 Die Länge eines Arrays über das Attribut length
Die Anzahl der Elemente, folglich die Länge des Arrays, ist für jedes Array-Objekt in der frei zugänglichen Objektvariablen length gespeichert. length ist eine public final int-Variable, deren Wert entweder positiv oder Null ist.
Beispiel Ein Feld und Ausgabe der Länge
int primiMäuschen[] = { 1, 2, 3, 5, 7, 7 + 4, };
System.out.println( primiMäuschen.length );
|
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.5.4 Zugriff auf die Elemente
Die Anzahl der Elemente, die ein Array aufnehmen kann, wird auch als Größe beziehungsweise als Länge bezeichnet. In Java beginnt ein Array, ähnlich wie in C(++), bei 0 (und nicht bei einer frei wählbaren Untergrenze wie in PASCAL). Die Größe lässt sich später nicht mehr ändern. Da die Elemente eines Arrays ab 0 nummeriert werden, ist der letzte gültige Index um 1 kleiner als die Länge des Felds. Der Zugriff auf die Elemente eines Felds erfolgt mithilfe der eckigen Klammern [], die hinter die Referenz an das Array-Objekt gesetzt werden. Bei einem Array a der Länge n ist der gültige Bereich somit a[0] bis a[n-1].
Beispiel Greife auf das erste und letzte Zeichen aus dem Feld zu.
char name[] = "Kalles Pommesbude".toCharArray();
char first = name[0]; // K
char last = name[name.length - 1]; // e
|
Über den Typ des Index
Innerhalb der eckigen Klammern steht ein Ganzzahl-Ausdruck, der sich zur Laufzeit berechnen lassen muss. long-Werte sowie Gleitkommazahlen sind nicht möglich. Bei Long-Werten wäre der Wertebereich zu groß, denn ein int-Index erlaubt ja schon mehr als 2,4 Milliarden Elemente. Bei Gleitkommazahlen bliebe die Frage nach der Zugriffstechnik. Hier müssten wir den Wert auf ein Intervall herunterrechnen.
Beispiel Liegt etwa eine Fließkommazahl f im Intervall von 0 bis 1 und haben die Werte eine Genauigkeit von einem Tausendstel, so ließe sich für ein Array a mit 1 000 Elementen für eine Konsolenausgabe schreiben:
double f = 0.01; // im Intervall von 0 bis 1
System.out.println( a[(int)(f*1000 )] );
|
Der Index vom Typ char ist auch ein int
Der Index eines Felds muss von einem Typ sein, der ohne Verlust auf int konvertierbar ist. Dazu gehören byte, short und char.
Beispiel Das Zeichen c soll in eine Java-Unicode-Zeichenfolge der Form \uxxxx umgewandelt werden.
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 );
}
private static final char hexchars[] = "0123456789".toCharArray();
|
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 das zusätzlich den Vorteil, dass keine eventuell unerwünschten Abhängigkeiten zu anderen Klassen entstehen.
3.5.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.
Beispiel Die zweite Zeile erzeugt ein Array-Objekt für 100 Elemente.
int arrayOfInts[];
arrayOfInts = new int[100];
Die Felder mit den primitiven Werten sind mit 0, 0.0 oder false initialisiert.
|
Die Deklaration ist auch zusammen mit der Zuweisung möglich.
Beispiel Deklaration eines 10-elementigen Felds und Initialisierung der Elemente
double x[] = new double[10]; // dann gilt für die Indexwerte 0 <= x <= 9
for ( int i = 0; i < 10; i++ )
x[i] = 2*i;
|
Beispiel Günstig ist ein Index vom Typ char, der automatisch zum int konvertiert wird, zum Beispiel als Laufvariable, wenn Felder von Zeichenketten generiert werden.
char alphabet[] = new char['z'-'a'+1];
for ( char c = 'a'; c <= 'z'; c++ )
alphabet[c-'a'] = c;
|
Das ist auch eine elegante und schnelle Möglichkeit, Strings zu erzeugen, denn die Zeichenkette kann mit einem String(char[])-Konstruktor in ein String-Objekt umgewandelt werden:
String result = new String( alphabet );
3.5.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 in dem folgenden Fall, bei dem der Compiler den Fehler nicht bemerkt:2
int feld[];
feld[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 nachfolgenden Zeilen:
int feld[] = new int[100];
feld[100] = 100;
Hier könnte der Compiler theoretisch Alarm schlagen, was aber kaum ein Compiler bisher macht, denn der Zugriff auf Elemente mit einem ungültigen Index ist syntaktisch und statisch semantisch völlig in Ordnung.
Ist der Index negativ3 oder zu groß, dann hagelt es eine IndexOutOfBoundException. Wird diese nicht abgefangen, bricht das Laufzeitsystem das Programm mit einer Fehlermeldung ab.
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.
a[i] = i++;
Bei der Position a[i] wird i gesichert und anschließend die Zuweisung gemacht. Wenn wir eine Schleife darum konstruieren, erweitern wir dies zu einer Initialisierung:
int is[] = new int[4];
int i = 0;
while ( i < is.length )
is[i] = i++;
Die Ausgabe ergibt 0, 1, 2 und 3. Von der Anwendung ist wegen mangelnder Übersicht abzuraten.
3.5.7 Arrays mit nichtprimitiven 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.
Beispiel Ein nichtprimitives Feld mit fünf Punkt-Objekten
Point punkte[] = new Point[5];
Hier wird Platz für fünf Verweise auf Punkt-Objekte gemacht, aber kein einziges Point-Objekt angelegt. Später würde das Feld etwa mit point[0] = new Point() gefüllt. Standardmäßig werden die Array-Elemente mit der null-Referenz initialisiert.
|
Beispiel Fünf Punkte werden angelegt und mit willkürlichen Werten gefüllt. Die Zufallszahlen werden dabei mithilfe der mathematischen Funktion Math.random() erzeugt.
Point punkte[] = new Point[5];
for ( int i = 0; i < punkte.length; i++ )
punkte[i] = new Point( (int)(Math.random()*100), (int)(Math.random()*100) );
for ( int i = 0; i < punkte.length; i++)
System.out.println( punkte[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.5.8 Arrays und Objekte
Wir haben gesehen, dass Arrays Objekte sind, die geordnete Elemente enthalten. In diesem Zusammenhang sind auch die Operatoren == und != zu deuten. Sie vergleichen lediglich, ob zwei Variablen auf das gleiche Array-Objekt verweisen, aber auf keinen Fall die Inhalte der Felder. (Das kann aber Arrays.equals()).
Trotz der weitgehenden Übereinstimmung mit gewöhnlichen Objekten sollten wir die Unterschiede nicht verschweigen:
|
Mit dem Operator [] kann auf Array-Elemente über ihren ganzzahligen Index zugegriffen werden. Dieser Operator wird bei anderen Objekten nicht angeboten. |
|
Eine spezielle Form des new-Operators erzeugt ein Exemplar der Array-Klasse. |
|
Eine entsprechende Array-Klasse wird automatisch generiert, wenn ein Array-Typ deklariert wird. |
3.5.9 Initialisierte Array-Objekte
Wenn wir in Java ein Array-Objekt erzeugen und gleich mit Werten initialisieren wollen, dann schreiben wir etwa:
int primi[] = { 2, 5, 7, 11, 13 };
Wollen wir uns erst nach der Variablendeklaration für die Feldinhalte interessieren und sie gegebenenfalls auch ändern, schlägt ein Versuch wie der Folgende fehl:
int primi[];
primi = { 2, 5, 7, 11, 13 };
Besonders ärgerlich wird dies bei der Parameterübergabe. So scheitert der folgende praktische Funktionsaufruf:
ausgleichsgerade( { 1.23, 4.94, 9.33, 3.91, 6.34 } );
Das Einzige, was spontan zur Lösung dieses Problems beiträgt, ist die Einführung einer neuen Variablen:
int primi[];
int tmpprimi[] = { 2, 5, 7, 11, 13 };
primi = tmpprimi;
Ein Feld ohne Namen
Glücklicherweise gibt eine Variante des new-Operators, der durch ein Paar eckiger Klammern erweitert wird. Es folgen in geschweiften Klammen 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 primi[];
primi = new int[]{ 2, 5, 7, 11, 13 };
ausgleichsgerade( 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 wird und keine zusätzliche Variable benutzt wird, wird diese Art der Arrays »anonyme Arrays« genannt. Eigentlich gibt es auch sonst anonyme Arrays, wie new int[2000].length zeigt. Doch in diesem Fall wird das Feld nicht mit Werten initialisiert.
Möglicherweise wird sich die Schreibweise bei Java 1.5 vereinfachen. So schlägt der JSR 65 »Concise Object-Array Literals« vor, dass direkt ein Paar geschweifter Klammern ein Feld von Objekten bildet.4
Variable Parameterlisten
Mit dem Konzept der initialisierten Array-Objekte lässt sich eine variable Parameterliste nachstellen. Wir schreiben einfach eine Methode, die ein Feld verlangt, und übergeben dann beim Aufruf ein entsprechend vorbelegtes Array-Objekt.
Listing 3.4 VariableParameter.java
public class VariableParameter
{
static double sum( double values[] )
{
double sum = 0;
for ( int i = 0; i < values.length; i++ )
sum += values[i];
return sum;
}
public static void main( String args[] )
{
System.out.println( sum(new double[]{ 1.5, 2.5 }) );
}
}
In Java 1.5 wird es dafür ein eigenes Konstrukt geben, welches automatisch ein anonymes Feld anlegt, und so variable Parameterlisten erlaubt.
3.5.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. Ein zweidimensionales Feld mit dem Platz für Reihen von acht Elementen definiert sich einfach über folgende Zeile:
int A[][] = new int[4][8];
Zwei alternative Deklarationen sind:
int[][] A = new int[4][8]; // Der Typ von A ist zweidimensionales Array
// mit Elementtyp int
int[] A [] = new int[4][8];
Einzelne Elemente werden mit A[i][j] angesprochen.5 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.
Beispiel Der Aufbau von zweidimensionalen Feldern ist vergleichbar mit einer Matrix beziehungsweise Tabelle. Dann lässt sich der Eintrag im Feld a[x][y] in folgender Tabelle ablesen:
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]
...
|
Nichtrechteckige 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.
Beispiel Ein dreieckiges Array mit Zeilen der Länge 1, 2 und 3.
int m[][] = new int[3][];
for ( int i = 0; i < 3; i++ )
m[i] = new int[i+1];
|
Der Vergleich von
int m[][] = new int [3][4];
int m[][] = new int [3][];
zeigt, dass im ersten Fall die passenden Unterfelder automatisch erzeugt werden. Dies ist im zweiten Fall nicht so. Hier müssen wir selbst die Unterfelder initialisieren, bevor wir auf die Elemente zugreifen.
Ebenso wie bei eindimensionalen Feldern lassen sich mehrdimensionale Felder gleich beim Anlegen initialisieren.
Beispiel Mehrdimensionale Felder initialisieren
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.
Das Pascal'sche Dreieck
Das folgende Beispiel zeigt eine weitere Anwendung von nichtrechteckigen Arrays, in dem 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.1 Das Pascal'sche Dreieck
Beispiel In der Implementierung wird zu jeder Ebene dynamisch ein Feld mit der passenden Länge angefordert. Die Ausgabe ist nicht - wie in der Grafik - zentriert.
|
Listing 3.5 Pascal.java
class Pascal
{
public static void main( String args[] )
{
int dreieck[][] = new int[7][];
for ( int i = 0; i < dreieck.length; i++ )
{
dreieck[i] = new int[i+1];
for ( int j = 0; j <= i; j++ )
{
if ( (j == 0) || (j == i) )
dreieck[i][j] = 1;
else
dreieck[i][j] = dreieck[i-1][j-1] + dreieck[i-1][j];
System.out.print( dreieck [i][j] + " " );
}
System.out.println();
}
}
}
Andere Anwendungen
Auf diese Art und Weise ist die Verwaltung von symmetrischen Matrizen einfach, denn eine solche Matrix enthält symmetrisch zur Diagonalen gleiche Elemente. Daher kann entweder die obere oder die untere Dreiecksmatrix entfallen. Besonders nützlich ist der Einsatz dieser effizienten Speicherform für Adjazenzmatrizen6 bei ungerichteten Graphen.
3.5.11 Die Wahrheit über die Array-Initialisierung
So schön die kompakte Initialisierung der Feldelemente ist, so laufzeit- und speicherintensiv ist sie. Da Java eine dynamische Sprache ist, passt das Konzept der Array-Initialisierung nicht ganz in das Bild. Und daher wird die Initialisierung auch erst zur Laufzeit durchgeführt. Bleiben wir bei unseren Primzahlen:
int primiMäuschen[] = { 1, 2, 3, 5, 7, 7 + 4 };
wird vom Java-Compiler umgeformt und analog zu Folgendem behandelt:
int primiMäuschen[] = new int[6];
primiMäuschen[0] = 1;
primiMäuschen[1] = 2;
primiMäuschen[2] = 3;
primiMäuschen[3] = 5;
primiMäuschen[4] = 7;
primiMäuschen[5] = 11;
Erst nach etwas Überlegung wird das erschreckende Ausmaß sichtbar. Zunächst ist es der Speicherbedarf für die Methoden. Ist das Feld primiMäuschen beispielsweise in einer lokalen Methode untergebracht, so 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, welches eine Datei in eine Folge von Array-Deklarationen verwandelte. Ist dies in Java wirklich nötig, dann 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, so 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.5.12 Arrays kopieren und füllen
Wollen wir eine Kopie eines Arrays mit gleicher Größe und gleichem Elementtyp schaffen, so nutzen wir dazu die Objektmethode clone(). Es klont - also in unserem Fall kopiert - die Elemente des Array-Objekts in ein neues. Die Kopie ist jedoch flach, was besagt, dass nur das Feld, aber nicht die referenzierten Objekte mitkopiert werden. Bei mehrdimensionalen Arrays wird also nur die erste Dimension kopiert, Unter-Arrays werden somit gemeinsam genutzt.
Beispiel Die clone()-Methode bei Feldern
int quelle[] = new int[6];
int ziel[] = (int[]) quelle.clone();
|
Eine weitere nützliche Funktion ist die statische Funktion arraycopy() der Klasse System. System.arraycopy() arbeitet auf zwei schon existierenden Feldern. Somit muss, anders als bei clone(), zuerst ein (leeres) Array-Objekt geschaffen werden, das mindestens so lang (groß) ist wie das ursprüngliche Array. arraycopy() eignet sich dazu, sich vergrößernde Felder zu implementieren. Natürlich kann die Methode auch dazu verwendet werden, Elemente eines Felds um bestimmte Positionen zu verschieben:
final class java.lang.System
|
static void arraycopy( Object src, int src_pos,
Object dst, int dst_pos, int length )
Kopiert length viele Objekte des Arrays src ab der Position src_pos in ein Array dst an die Stelle dst_pos.
Hier klicken, um das Bild zu Vergrößern
Abbildung 3.2 Kopieren der Elemente von einem Feld in ein anderes
Beispiel Bewege alle Elemente eines Feldes f um eine Stelle nach links/rechts.
System.arraycopy( f, 1, f, 0, f.length - 1 ); // links
System.arraycopy( f, 0, f, 1, f.length - 1 ); // rechts
|
3.5.13 Mehrere Rückgabeparameter
Wenn wir in Java Funktionen schreiben, dann haben diese höchstens einen Rückgabewert. Wollen wir aber mehr als einen Wert zurückgeben, müssen wir eine andere Lösung suchen, die häufig so aussieht, dass ein Objekt die Rückgabewerte zusammenfasst. Solch ein Objekt kann auch ein Array sein. Betrachten wir eine Methode, die für zwei Zahlen die Summe und das Produkt liefert.
Listing 3.6 Prodsum.java
public class ProdSum
{
static int[] prodSum( int a, int b )
{
return new int[]{ a*b, a+b };
}
public static void main( String args[] )
{
System.out.println( prodSum(9,3)[1] );
}
}
3.5.14 Parameter per Referenz übergeben
Ob es nun Werte primitiver Typen oder Referenzen sind, in Java werden alle Parameter als Kopie per Wert übergeben. Wollen wir die Parameterübergabe per Referenz simulieren und somit der Methode erlauben, die übergebenen Parameterwerte nach außen hin sichtbar zu ändern, so ist es eine gute Lösung, ein Array zu benutzen.
Beispiel Über Felder Werte per Referenz übergeben
void foo()
{
int a[] = { 4 };
inc( a );
System.out.println( a[0] );
}
void inc( int a[] )
{
a[0]++;
}
|
3.5.15 Die Klasse Arrays
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.
Zwei Felder vergleichen, Arrays.equals()
Die Funktion Arrays.equals() vergleicht, ob zwei Felder die gleichen Inhalte besitzen. Dazu ist die Funktion für alle wichtigen Typen definiert, sodass es 9 Varianten gibt. Die Rückgabe der Funktion ist true, falls ja, 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. Einen Vergleich von Teilfeldern ist leider nicht vorgesehen.
Felder zu Listen, Arrays.asList()
Nehmen wir an, wir haben es mit einem Feld von Hundenamen zu tun, welches wir auf dem Bildschirm ausgeben wollen.
String hundenamen[] = {
"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 und liefert eine Zeichenkette wie
[Ljava.lang.String;@111f71
Die Methode Arrays.asList() eignet sich gut dazu, ein Feld auszugeben:
System.out.println( Arrays.asList(hundenamen) );
Das spart eine for-Schleife, die durch das Feld läuft und auf jedem Element print() aufruft. Die Klasse Arrays liegt im Paket java.util.
3.5.16 Der Einstiegspunkt für das Laufzeitsystem main()
In Java-Klassen gibt es - ähnlich wie in C(++) - eine ausgezeichnete Funktion main(), die das Laufzeitsystem in der angegebenen Hauptklasse (oder Startklasse) des Programms aufruft. Die main()-Funktion ist für alle Klassen und in JVM zugänglich (public) und auf jeden Fall statisch (static) zu deklarieren. Die Methode muss statisch sein, da auch ohne Exemplar der Klasse ein Funktionsaufruf möglich sein soll. Als Parameter wird ein Array von Zeichenketten angenommen. In diesem sind die auf der Kommandozeile übergebenen Parameter gespeichert:
public static void main( String args[] )
Stimmt der Kopf der Methode nicht, dann wird diese Funktion nicht als Einstiegspunkt von der virtuellen Maschine erkannt.
Hinweis Im Gegensatz zu C(++) steht im ersten Element des Argument-Arrays mit Index 0 nicht der Programmname, also der Name der Hauptklasse, sondern bereits der erste Programmparameter der Kommandozeile.
|
3.5.17 Die Anzahl der Parameter
Eine besondere Variable für die Anzahl der Parameter ist nicht vonnöten, da das String-Array-Objekt selbst weiß, wie viele Parameter es enthält. Dem folgenden Programm können wir bei der Ausführung hinter dem Klassennamen noch einen Vornamen auf der Kommandozeile übergeben. Dieser wird dann auf der Standardausgabe ausgegeben. Wir können eine Schleife verwenden, um alle Kommandozeilenparameter auszugeben.
Listing 3.7 LiebtHamster.java
class LiebtHamster
{
public static void main( String args[] )
{
if ( args.length == 0 )
System.out.println( "Was?! Keiner liebt kleine Hamster?" );
else
{
System.out.print( "Liebt kleine Hamster: " );
for ( int i = 0; i < args.length; i++ )
System.out.print( args[i] + " " );
System.out.println();
}
}
}
Wir können das Programm auf der Kommandozeile wie folgt aufrufen:
$ java LiebtHamster Raphael Regina Paul Mirjam
3.5.18 Der Rückgabewert von main() und System.exit()
Der Rückgabetyp void ist sicherlich diskussionswürdig, da die Sprachentwerfer auch hätten fordern können, dass ein Programm immer einen Statuscode an das aufrufende Programm zurückgibt. Für diese Lösung haben sie sich aber nicht entschieden, da Java-Programme in der Regel nur minimal mit dem umgebenden Betriebssystem interagieren sollen und echte Plattformunabhängigkeit gefordert ist, etwa bei Java in Handys. Für die Fälle, in denen ein Statuscode zurückgeliefert werden soll, steht die Funktion exit(status) der Klasse System zur Verfügung; sie beendet eine Applikation. Der Parameter der Funktion ist der Statuswert (exit status), der an die Kommandozeile zurückgegeben wird. Der Wert ist für Skriptprogramme wichtig, denn sie können über diesen Rückgabewert auf das Gelingen oder Misslingen des Java-Programms reagieren. Ein Wert von 0 zeigt per Definition das Gelingen an, ein Wert ungleich 0 einen Fehler. Der Wertebereich sollte sich zwischen 0 und 255 bewegen. Unter der Unix-Kommandozeile ist der Rückgabewert eines Programms unter $? verfügbar.
final class java.lang.System
|
|
static void exit( int status ) |
|
Ein Aufruf von exit() beendet die aktuelle JVM und gibt das Argument der Methode als Statuswert zurück. Ein Wert ungleich Null zeigt einen Fehler an. Also ist der Rückgabewert beim normalen fehlerfreien Verlassen Null. Eine SecurityException wird geworfen, falls der aktuelle SecurityManager dem aufrufenden Code nicht erlaubt, JVM zu beenden. Das gilt insbesondere bei Applets in einem Web-Browser. |
3.5.19 Parser der Kommandozeilenargumente Apache CLI
Zum Parsen der Kommandozeilenargumente bietet sich ein externes Paket von Apache an: CLI (http://jakarta.apache.org/commons/cli/). Um Optionen aufzubauen, ist als Erstes ein Options-Objekt nötig. Die Klassen befinden sich unter dem Paket org.apache.commons.cli.
Options options = new Options();
Diesem können nun einzelne Optionen mithilfe der Funktion addOption() hinzugefügt werden. Nehmen wir an, wir wollten unser Applikation eine Option -h erlauben. Wenn sie angegeben wird, also auf der Kommandozeile -h auftaucht, soll eine Hilfe ausgegeben werden.
options.addOption( "h", false, "Hilfe anzeigen" );
Der erste Parameter ist der Name der Option, die zweite Angabe eine Aussage, ob die Option Argumente hat oder nicht, und der dritte Parameter ist ein Informationsstring. addOption() kann auch mit einem Option-Objekt parametrisiert werden. Für unsere Wahrheitsoption heißt dies:
Option hilfe = new Option( "h", "Hilfe anzeigen" );
options.addOption( hilfe );
Die Option-Objekte werden bei Argumenten interessant, die weitere Angaben enthalten. Ein Beispiel für die Angabe auf der Kommandozeile:
$ java Programm -size=8000 -file dumdum.txt
Die Option size erwartet ein Argument nach dem Gleichheitszeichen. Die Option kann zum Beispiel mit OptionBuilder.withLongOpt() konstruiert werden.
options.addOption( OptionBuilder.withLongOpt( "size" )
.withDescription( "size zur Angabe der Größe" )
.withValueSeparator( '=' )
.hasArg()
.create() );
Um die Argumente abzufragen, muss nach dem Aufbau des Option-Objekts das konkrete Argument der Kommandozeile geparst werden. Dazu dient die Funktion parse(String args[]).
CommandLine cmd = options.parse(args);
Das CommandLine-Objekt bietet hasOption(String), mit dem eine Option abgefragt werden kann. Für unsere Wahrheitsoption heißt das:
if ( cmd.hasOption("h") )
// Hilfe-Option ist gesetzt
Die Argumente der Optionen mit Argumenten werden mit getOptionValue(String) erfragt. Bei unser Größenangabe wäre dies:
String size = options.getOptionValue( "size" );
if ( size == null ) {
// Nicht gesetzt.
}
else {
// size ist gesetzt.
}
Auch mit hasOption() lässt sich erfragen, ob das Argument überhaupt gesetzt ist.
Die CLI-Bibliothek kann automatisch schick eine Hilfe mit den Optionen anzeigen. Dazu dient die Funktion printHelp() aus HelpFormatter.
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp( "programmname", options );
Ein boolean-Argument lässt sich noch anhängen und gibt dann noch mehr Informationen an.
1 Dies kommt in C++ allerdings wieder in Mode und macht auch Sinn. Aber das ist eine andere Geschichte, die an anderer Stelle erzählt werden muss. In C++ gehören Typ-Konstruktionsoperatoren wie *, & und [] auch zu den deklarierten Variablen. In Java dagegen gibt es eben keine Variablen, die ein Array von Strings speichern, sondern es gibt nur Variablen vom Referenztyp »eindimensionales Array mit Elementtyp String«.
2 Obwohl er sich bei nicht initialisierten lokalen Variablen auch beschwert.
3 Ganz anders verhält sich da 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.
4 Allerdings bleibt das Problem, vom welchem Typ eine Liste wie 1, 2 ist. Sie wird wohl in ein new Object[] new Integer(1), new Integer(2) übersetzt, aber nicht in ein new int[] 1,2 .
5 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.
6 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).
|