![]() |
|
|||||
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.
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 ArraysEin 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.
3.5.3 Die Länge eines Arrays über das Attribut length
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Beispiel Ein Feld und Ausgabe der Länge
int primiMäuschen[] = { 1, 2, 3, 5, 7, 7 + 4, }; System.out.println( primiMäuschen.length ); |
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.
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 |
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 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.
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. |
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 );
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.
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.
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] |
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. |
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;
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
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.
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] ... |
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 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(); } } }
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.
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.) |
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 |

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 |
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] ); } }
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]++; } |
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.
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.
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.
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. |
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
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. |
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).
| << zurück |
Copyright (c) Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.