Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5 (Tiger-Release)
Buch: Java ist auch eine Insel
gp Kapitel 3 Klassen und Objekte
  gp 3.1 Objektorientierte Programmierung
    gp 3.1.1 Warum überhaupt OOP?
    gp 3.1.2 Modularität und Wiederverwertbarkeit
  gp 3.2 Klassen benutzen
    gp 3.2.1 Die Klasse Point
    gp 3.2.2 Etwas über die UML
    gp 3.2.3 Anlegen eines Exemplars einer Klasse mit new
    gp 3.2.4 Zugriff auf Variablen und Methoden mit dem ».«
    gp 3.2.5 Konstruktoren
  gp 3.3 Import und Pakete
  gp 3.4 Die API-Dokumentation
  gp 3.5 Mit Referenzen arbeiten
    gp 3.5.1 Die null-Referenz
    gp 3.5.2 Zuweisungen bei Referenzen
    gp 3.5.3 Funktionen mit nichtprimitiven Parametern
    gp 3.5.4 Gleichheit von Objekten und die Methode equals()
  gp 3.6 Arrays
    gp 3.6.1 Deklaration von Arrays
    gp 3.6.2 Arrays mit Inhalt
    gp 3.6.3 Die Länge eines Arrays über das Attribut length
    gp 3.6.4 Zugriff auf die Elemente
    gp 3.6.5 Array-Objekte erzeugen
    gp 3.6.6 Fehler bei Arrays
    gp 3.6.7 Arrays mit nicht-primitiven Elementen
    gp 3.6.8 Initialisierte Array-Objekte
    gp 3.6.9 Die erweiterte for-Schleife
    gp 3.6.10 Mehrdimensionale Arrays
    gp 3.6.11 Die Wahrheit über die Array-Initialisierung
    gp 3.6.12 Mehrere Rückgabewerte
    gp 3.6.13 Argument per Referenz übergeben
    gp 3.6.14 Arrays klonen
    gp 3.6.15 Feldinhalte kopieren
    gp 3.6.16 Die Klasse Arrays zum Vergleichen, Füllen, Suchen
    gp 3.6.17 Methode mit variabler Argumentanzahl (vararg)
  gp 3.7 Der Einstiegspunkt für das Laufzeitsystem main()
    gp 3.7.1 Kommandozeilen-Parameter ausgeben
    gp 3.7.2 Der Rückgabewert von main() und System.exit()
    gp 3.7.3 Parser der Kommandozeilenargumente Apache CLI
  gp 3.8 Eigene Pakete schnüren
    gp 3.8.1 Die package-Anweisung
    gp 3.8.2 Importieren von Klassen mit import
    gp 3.8.3 Paketnamen
    gp 3.8.4 Hierarchische Strukturen und das Default-Package
    gp 3.8.5 Klassen mit gleiche Namen in unterschiedlichen Paketen
    gp 3.8.6 Statische Imports
    gp 3.8.7 Eine Verzeichnisstruktur für eigene Projekte


Galileo Computing

3.6 Arraydowntop

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:

gp  Elementare Datentypen wie int, byte, long und so weiter
gp  Referenzen auf Objekte
gp  Referenzen auf andere Arrays, um mehrdimensionale Arrays zu realisieren

Galileo Computing

3.6.1 Deklaration von Arrays  downtop

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[];

Eine Variable wie schachfeld hat jetzt den Typ »ist Feld« und »speichert int-Elemente«, also eigentlich zwei Typen.

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. Das beugt Fehlern vor. Nach reiner Java-Lehre jedenfalls gehören die Klammern hinter den Typbezeichner, so hat es Gosling gewollt.


Galileo Computing

3.6.2 Arrays mit Inhalt  downtop

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, so dass aus einem Feld mit Zeichen ein String-Objekt erzeugt werden kann. Alle Zeichen des Felds werden kopiert, so dass 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();



Galileo Computing

3.6.3 Die Länge eines Arrays über das Attribut length  downtop

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.


Galileo Computing

3.6.4 Zugriff auf die Elemente  downtop

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.length1];         // e

Über den Typ des Index

Innerhalb der eckigen Klammern steht ein positiver Ganzzahl-Ausdruck, der sich zur Laufzeit berechnen lassen muss. long-Werte sowie Gleitkommazahlen 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.


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. 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];
for ( char c =a; c <=z; c++ )
  alphabet[c – ’a] = c;

Um aus dem Alphabet ein String-Objekt zu erzeugen, nutzen wir den String(char[])-Konstruktor:


String result = new String( alphabet );

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.


Galileo Computing

3.6.5 Array-Objekte erzeugen  downtop

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.


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;

Dass Arrays Objekte sind, zeigen einige Indizien:

gp  Eine spezielle Form des new-Operators erzeugt ein Exemplar der Array-Klasse; new erinnert uns immer daran, dass ein Objekt zur Laufzeit aufgebaut wird.
gp  Auf dem Array-Objekt sind Methoden – wie clone(), und alles was java.lang.Object hat – definiert und ein Array-Objekt kennt das Attribut length.
gp  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. Dieser Operator wird bei anderen Objekten nicht angeboten.


Galileo Computing

3.6.6 Fehler bei Arrays  downtop

Beim Zugriff auf ein Array-Element können Fehler auftreten. Zunächst einmal kann das Array-Objekt fehlen, so dass die Referenzierung fehlschlägt. Etwa in dem folgenden Fall, bei dem der Compiler den Fehler nicht bemerkt:


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 negativ oder zu groß, dann hagelt es eine IndexOutOfBoundException. 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.

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.


Galileo Computing

3.6.7 Arrays mit nicht-primitiven Elementen  downtop

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. Da die Zufallsfunktion jedoch Fließkommazahlen zwischen 0 und 1 liefert, werden die Zahlen durch Multiplikation frisiert und dann geschnitten.

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]


Galileo Computing

3.6.8 Initialisierte Array-Objekte  downtop

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 };
ausgleichsgerade( { 1.23, 4.94, 9.33, 3.91, 6.34 } );

Zur Lösung gibt es zwei Ansätze. Die erste ist die Einführung einer neuen Variablen:


int primi[];
int tmpprimi[] = { 2, 5, 7, 11, 13 };
primi = tmpprimi;

Dann gibt es 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.


Galileo Computing

3.6.9 Die erweiterte for-Schleife  downtop

for-Schleifen laufen oft Felder oder Datenstrukturen ab. Nehmen wir als Beispiel die Argumente der Kommandozeile, die sich im Feld args[] befinden. Sie sollen auf dem Bildschirm ausgegeben werden.


public static void main( String args[] )
{
  for ( int i = 0; i < args.length; i++ )
    System.out.println( args[i] );
}

Auffällig ist, dass i eigentlich gar keine große Rolle spielt, sondern lediglich als Index seine Berechtigung hat; nur damit lässt sich das Element an einer bestimmten Stelle im Feld ansprechen.

Da dieses 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 Bezeicher : Feld )
  ...

Diese erweiterte Form der for-Schleife löst sich vom Index und erfragt jedes Element des Feldes. Das lässt sich als Menge vorstellen, denn der Doppelpunkt liest sich als »in«. Formulieren wie unser Beispiel mit dem erweiterten for.


class Ablaeufer
{
  public static void main( String args[] )
  {
    for ( String arg : args )
      System.out.println( arg );
  }
}

Zu lesen ist die for-Zeile demnach mit »Für jedes Element arg vom Typ String in args tue...«. Ein Variablenname wie i für den Schleifenindex ist nicht mehr nötig, denn der Index ist nicht sichtbar.

Intern setzt der Compiler diese erweiterte for-Schleife ganz klassisch um, so dass der Bytecode unter beiden Varianten gleich ist. Der Nachteil dieser Variante kann sein, dass sie immer alle Elemente des Feldes abläuft und ein Anfang und Ende nicht ausdrücklich gesetzt werden kann sowie auch nur immer von vorne nach hinten zählt. Ein Feld von hinten nach vorne durchlaufen kann weiterhin nur eine klassische for-Schleife.


Beispiel   Bestimme den Mittelwert für Fließkommazahlen durch die Funktion avg().

double avg( double array[] )
{
  if ( array == null || array.length == 0 )
    throw new IllegalArgumentException( "Illegales Feld" );

  double sum = 0;

  for ( double n : array )
    sum += n;

  return sum / array.length;
}


Galileo Computing

3.6.10 Mehrdimensionale Arrays  downtop

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. 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.3   Das Pascal’sche Dreieck

In der Implementierung wird zu jeder Ebene dynamisch ein Feld mit der passenden Länge angefordert. Die Ausgabe wird mit der neuen Java 5-Funktion printf() getätigt, da wir auf diese Weise ein führendes Leerzeichen bekommen, wenn die Zahl nur aus einer Ziffer besteht.

Listing 3.4   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.printf( "%2d ", 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 Adjazenzmatrizen bei ungerichteten Graphen.


Galileo Computing

3.6.11 Die Wahrheit über die Array-Initialisierung  downtop

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:

gp  Wir verwenden ein statisches Feld (eine Klassenvariable), so dass das Array nur einmal während des Programmlaufs initialisiert werden muss.
gp  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.)

Galileo Computing

3.6.12 Mehrere Rückgabewerte  downtop

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.5   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] );
  }
}

Galileo Computing

3.6.13 Argument per Referenz übergeben  downtop

Ob es nun Werte primitiver Typen oder Referenzen sind, in Java werden alle Argumente als Kopie per Wert übergeben. Wollen wir die Parameterübergabe per Referenz simulieren und somit der Methode erlauben, die übergebenen Argumentwerte 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]++;
}


Galileo Computing

3.6.14 Arrays klonen  downtop

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();



Galileo Computing

3.6.15 Feldinhalte kopieren  downtop

Eine weitere nützliche Funktion ist die statische Funktion System.arraycopy(). Sie kann auf zwei Arten arbeiten:

gp  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, in dem erst ein neues größeres Feld angelegt wird und anschließend die alten Feldinhalte in das neue Feld kopiert werden.
gp  Auf dem gleichen Feld. So kann die Methode dazu verwendet werden, Elemente eines Felds um bestimmte Positionen zu verschieben. Die Bereiche können sich durchaus überlappen.


final class java.lang.  System  

gp  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.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 3.4   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.length1 );   // links
System.arraycopy( f, 0, f, 1, f.length1 );   // rechts


Galileo Computing

3.6.16 Die Klasse Arrays zum Vergleichen, Füllen, Suchen  downtop

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, so dass 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.


int is[] = { 1, 2, 3, 4 };
int clone[] = (int[]) is.clone();
System.out.println( Arrays.equals( is, clone ) );  // true

Einen Vergleich von Teilfeldern ist leider auch nach mehr als sieben Jahren Java-Bibliothek nicht vorgesehen.

Arrays.equals() vergleicht Objektfelder nicht per ==, sondern per equals(). Seit Java 5 ist es möglich, über deepEquals() auch tief zu vergleichen. Das heißt, wenn ein Feld ein weiteres Feld referenziert, wird auch dieses überprüft.

Füllen von Feldern

Arrays.fill() füllt ein Feld mit einem festen Wert. Der Start-Endbereich lässt sich optional angeben.

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 API-Dokumentation der Funktion ist durch die Verwendung der generischen Funktionen etwas schwieriger.



class java.util.  Arrays  

gp  static <T> List<T> asList( T[] a )
Liefert eine Liste vom Typ T bei einem Feld vom Typ T.

Seit Java 5 gesellt sich die Funktion toString(Feld) dazu, die ebenfalls eine gewünschte String-Repräsentation des Feldes gibt:


System.out.println( Arrays.toString(hundenamen) );

Suchen im Feld

Ist das Feld sortiert, lässt sich mit Array.binarySearch() eine binäre Suche (Halbierungssuche) durchführen. Wenn das Feld nicht sortiert ist, funktioniert das nicht und die Java-Bibliothek hat für diesen Fall auch keine Funktion im Angebot. Hier muss eine eigene Schleife her, oder das Feld ist über asList() in eine java.util.List zu konvertieren und dann mit contains() zu überprüfen.


Beispiel   Um zu testen, ob auf der Kommandozeile der Schalter -? gesetzt ist, lässt sich schreiben:

if ( Arrays.asList( args ).contains( "-?" ) )

...

Sortieren

Diverse sort()-Funktionen ermöglichen das Sortieren von Elementen im Feld. Im Fall von Objekten müssen sie vergleichbar sein. Das gelingt entweder mit einem extra Comparator, oder die Klassen implementieren die Schnittstelle Comparable. Im Kapitel über Datenstrukturen und Algorithmen wird das präzise beschrieben.


Galileo Computing

3.6.17 Methode mit variabler Argumentanzahl (vararg)  toptop

Bei vielen Methoden ist es klar, wie viele Argumente sie haben; eine Sinus-Funktion bekommt sowieso nur ein Argument. Es gibt jedoch Funktionen, wo die Zahl mehr oder weniger frei ist, etwa bei der Funktion max(). Die Definition aus java.lang.Math sieht eine max()-Funktion mit zwei Argumenten vor, doch grundsätzlich könnte die Funktion auch ein Feld entgegen nehmen und von diesen Elementen das Maximum bilden. Java 5 sieht eine weitere Möglichkeit vor: Methoden mit variabler Argumentanzahl, 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 )
{
}

In der Funktion max() wird args jetzt so behandelt wie ein Feld. Da wir die 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 Typen in der Signatur definiert, so dürfen sie nur den letztes Parameter bilden; andernfalls könnte der Compiler bei den Parametern nicht unbedingt zuordnen, was nun ein Vararg ist und was schon der nächste gefüllte Parameter.

Listing 3.6   MaxVarArgs.java


public class MaxVarArgs
{
  public static int max( int... 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 im Hintergrund ein Feld mit drei Elementen angelegt wird.






1   Obwohl er sich bei nicht initialisierten lokalen Variablen auch beschwert.

2   Ganz anders verhält 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.

3   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.

4   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).

5  Das ist gültig, da Arrays die Schnittstelle Cloneable implementieren.





Copyright © 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.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de