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

 
Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Eigene Klassen schreiben
6 Objektorientierte Beziehungsfragen
7 Ausnahmen müssen sein
8 Äußere.innere Klassen
9 Besondere Typen der Java SE
10 Generics<T>
11 Lambda-Ausdrücke und funktionale Programmierung
12 Architektur, Design und angewandte Objektorientierung
13 Komponenten, JavaBeans und Module
14 Die Klassenbibliothek
15 Einführung in die nebenläufige Programmierung
16 Einführung in Datenstrukturen und Algorithmen
17 Einführung in grafische Oberflächen
18 Einführung in Dateien und Datenströme
19 Einführung ins Datenbankmanagement mit JDBC
20 Einführung in <XML>
21 Testen mit JUnit
22 Bits und Bytes und Mathematisches
23 Die Werkzeuge des JDK
A Java SE-Paketübersicht
Stichwortverzeichnis


Download:

- Beispielprogramme, ca. 35,4 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil 3 Klassen und Objekte
Pfeil 3.1 Objektorientierte Programmierung (OOP)
Pfeil 3.1.1 Warum überhaupt OOP?
Pfeil 3.1.2 Denk ich an Java, denk ich an Wiederverwendbarkeit
Pfeil 3.2 Eigenschaften einer Klasse
Pfeil 3.2.1 Klassenarbeit mit Point
Pfeil 3.3 Natürlich modellieren mit der UML (Unified Modeling Language) *
Pfeil 3.3.1 Hintergrund und Geschichte der UML
Pfeil 3.3.2 Wichtige Diagrammtypen der UML
Pfeil 3.3.3 UML-Werkzeuge
Pfeil 3.4 Neue Objekte erzeugen
Pfeil 3.4.1 Ein Exemplar einer Klasse mit dem Schlüsselwort new anlegen
Pfeil 3.4.2 Der Zusammenhang von new, Heap und Garbage-Collector
Pfeil 3.4.3 Deklarieren von Referenzvariablen
Pfeil 3.4.4 Jetzt mach mal ’nen Punkt: Zugriff auf Objektattribute und -methoden
Pfeil 3.4.5 Überblick über Point-Methoden
Pfeil 3.4.6 Konstruktoren nutzen
Pfeil 3.5 ZZZZZnake
Pfeil 3.6 Pakete schnüren, Imports und Kompilationseinheiten
Pfeil 3.6.1 Java-Pakete
Pfeil 3.6.2 Pakete der Standardbibliothek
Pfeil 3.6.3 Volle Qualifizierung und import-Deklaration
Pfeil 3.6.4 Mit import p1.p2.* alle Typen eines Pakets erreichen
Pfeil 3.6.5 Hierarchische Strukturen über Pakete
Pfeil 3.6.6 Die package-Deklaration
Pfeil 3.6.7 Unbenanntes Paket (default package)
Pfeil 3.6.8 Klassen mit gleichen Namen in unterschiedlichen Paketen *
Pfeil 3.6.9 Kompilationseinheit (Compilation Unit)
Pfeil 3.6.10 Statischer Import *
Pfeil 3.7 Mit Referenzen arbeiten, Identität und Gleichheit (Gleichwertigkeit)
Pfeil 3.7.1 null-Referenz und die Frage der Philosophie
Pfeil 3.7.2 Alles auf null? Referenzen testen
Pfeil 3.7.3 Zuweisungen bei Referenzen
Pfeil 3.7.4 Methoden mit Referenztypen als Parametern
Pfeil 3.7.5 Identität von Objekten
Pfeil 3.7.6 Gleichheit (Gleichwertigkeit) und die Methode equals(…)
Pfeil 3.8 Arrays
Pfeil 3.8.1 Grundbestandteile
Pfeil 3.8.2 Deklaration von Arrays
Pfeil 3.8.3 Arrays mit Inhalt
Pfeil 3.8.4 Die Länge eines Arrays über das Attribut length auslesen
Pfeil 3.8.5 Zugriff auf die Elemente über den Index
Pfeil 3.8.6 Array-Objekte mit new erzeugen
Pfeil 3.8.7 Typische Array-Fehler
Pfeil 3.8.8 Arrays als Methodenparameter
Pfeil 3.8.9 Vorinitialisierte Arrays
Pfeil 3.8.10 Die erweiterte for-Schleife
Pfeil 3.8.11 Arrays mit nichtprimitiven Elementen
Pfeil 3.8.12 Methode mit variabler Argumentanzahl (Vararg)
Pfeil 3.8.13 Mehrdimensionale Arrays *
Pfeil 3.8.14 Nichtrechteckige Arrays *
Pfeil 3.8.15 Die Wahrheit über die Array-Initialisierung *
Pfeil 3.8.16 Mehrere Rückgabewerte *
Pfeil 3.8.17 Klonen kann sich lohnen – Arrays vermehren *
Pfeil 3.8.18 Array-Inhalte kopieren *
Pfeil 3.8.19 Die Klasse Arrays zum Vergleichen, Füllen, Suchen, Sortieren nutzen
Pfeil 3.8.20 Eine lange Schlange
Pfeil 3.9 Der Einstiegspunkt für das Laufzeitsystem: main(…)
Pfeil 3.9.1 Korrekte Deklaration der Startmethode
Pfeil 3.9.2 Kommandozeilenargumente verarbeiten
Pfeil 3.9.3 Der Rückgabetyp von main(…) und System.exit(int) *
Pfeil 3.10 Grundlagen von Annotationen und Generics
Pfeil 3.10.1 Generics
Pfeil 3.10.2 Annotationen
Pfeil 3.10.3 Annotationstypen aus java.lang
Pfeil 3.10.4 @Deprecated
Pfeil 3.10.5 @SuppressWarnings
Pfeil 3.11 Zum Weiterlesen
 

Zum Seitenanfang

3.8Arrays Zur vorigen ÜberschriftZur nächsten Überschrift

Ein Array (auch deutsch Feld oder Reihung genannt) ist ein spezieller Datentyp, der mehrere Werte zu einer Einheit zusammenfasst. Er ist mit einem Setzkasten vergleichbar, in dem die Plätze durchnummeriert sind. Angesprochen werden die Elemente über einen ganzzahligen Index. Jeder Platz (etwa für Schlümpfe) nimmt immer Werte des gleichen Typs auf (nur Schlümpfe und keine Pokémons). Normalerweise liegen die Plätze eines Arrays (seine Elemente) im Speicher hintereinander, doch ist dies ein für Programmierer nicht sichtbares Implementierungsdetail der virtuellen Maschine.

Jedes Array beinhaltet Werte nur eines bestimmten Datentyps bzw. Grundtyps. Dies können sein:

  • elementare Datentypen wie int, byte, long usw.

  • Referenztypen

  • Referenztypen anderer Arrays, um mehrdimensionale Arrays zu realisieren

 

Zum Seitenanfang

3.8.1Grundbestandteile Zur vorigen ÜberschriftZur nächsten Überschrift

Für das Arbeiten mit Arrays müssen wir drei neue Dinge kennenlernen:

  1. das Deklarieren von Array-Variablen

  2. das Initialisieren von Array-Variablen, Platzbeschaffung

  3. den Zugriff auf Arrays, den lesenden Zugriff ebenso wie den schreibenden

[zB]Beispiel

1. Deklariere eine Variable randoms, die ein Array referenziert:

double[] randoms;

2. Initialisiere die Variable mit einem Array-Objekt der Größe 10:

randoms = new double[ 10 ];

3. Belege das erste Element mit einer Zufallszahl und das zweite Element mit dem Doppelten des ersten Elements:

randoms[ 0 ] = Math.random();

randoms[ 1 ] = randoms[ 0 ] * 2;

Die drei Punkte schauen wir uns nun detaillierter an.

 

Zum Seitenanfang

3.8.2Deklaration von Arrays Zur vorigen ÜberschriftZur nächsten Überschrift

Eine Array-Variablendeklaration ähnelt einer gewöhnlichen Deklaration, nur dass nach dem Datentyp die Zeichen [ und ] gesetzt werden.

[zB]Beispiel

Deklariere zwei Array-Variablen:

int[] primes;

Point[] points;

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

[»]Hinweis

Die eckigen Klammern lassen sich bei der Deklaration einer Array-Variablen auch hinter den Namen setzen, doch ganz ohne Unterschied ist die Deklaration nicht. Das zeigt sich spätestens dann, wenn mehr als eine Variable deklariert wird:

int []primes,

matrix[], threeDimMatrix[][];

Das entspricht dieser Deklaration:

int primes[], matrix[][], threeDimMatrix[][][];

Damit Irrtümer dieser Art ausgeschlossen werden, sollten Sie in jeder Zeile nur eine Deklaration eines Typs schreiben. Nach reiner Java-Lehre gehören die Klammern jedenfalls hinter den Typbezeichner, so hat es der Java-Schöpfer James Gosling gewollt.

 

Zum Seitenanfang

3.8.3Arrays mit Inhalt Zur vorigen ÜberschriftZur nächsten Überschrift

Die bisherigen Deklarationen von Array-Variablen erzeugen noch lange kein Array-Objekt, das die einzelnen Array-Elemente aufnehmen kann. Wenn allerdings die Einträge direkt mit Werten belegt werden sollen, gibt es in Java eine Abkürzung, die ein Array-Objekt anlegt und zugleich mit Werten belegt.

[zB]Beispiel

Wertebelegung eines Arrays bei der Initialisierung:

int[] primes = { 2, 3, 5, 7, 7 + 4 };

String[] strings = {

"Haus", "Maus",

"dog".toUpperCase(), // DOG

new java.awt.Point().toString(),

};

In diesem Fall wird ein Array mit passender Größe angelegt, und die Elemente, die in der Aufzählung genannt sind, werden in das Array kopiert. Innerhalb der Aufzählung kann abschließend ein Komma stehen, wie die Aufzählung bei strings demonstriert.

[»]Hinweis

Es ist möglich, dass vor der schließenden geschweiften Klammer noch ein Komma folgt, sodass es etwa int[] primes = { 2, 3, }; heißt. Das vereinfacht das Hinzufügen, ein leeres Element produziert es nicht. Selbst Folgendes ist in Java möglich: int[] primes = { , };.

 

Zum Seitenanfang

3.8.4Die Länge eines Arrays über das Attribut length auslesen Zur vorigen ÜberschriftZur nächsten Überschrift

Die Anzahl der Elemente, die ein Array aufnehmen kann, wird Größe oder Länge genannt und 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. Die Größe lässt sich später nicht mehr ändern.

[zB]Beispiel

Ein Array und die Ausgabe der Länge:

int[] primes = { 2, 3, 5, 7, 7 + 4 };

System.out.println( primes.length ); // 5

Array-Längen sind final

Das Attribut length eines Arrays ist nicht nur öffentlich (public) und vom Typ int, sondern natürlich auch final. Schreibzugriffe sind nicht gestattet, denn eine dynamische Vergrößerung eines Arrays ist nicht möglich; ein Schreibzugriff führt zu einem Übersetzungsfehler.

 

Zum Seitenanfang

3.8.5Zugriff auf die Elemente über den Index Zur vorigen ÜberschriftZur nächsten Überschrift

Der Zugriff auf die Elemente eines Arrays erfolgt mithilfe der eckigen Klammern [], die hinter die Referenz an das Array-Objekt gesetzt werden. In Java beginnt ein Array beim Index 0 (und nicht bei einer frei wählbaren Untergrenze wie in Pascal). Da die Elemente eines Arrays ab 0 nummeriert werden, ist der letzte gültige Index um 1 kleiner als die Länge des Arrays. Das heißt: Bei einem Array a der Länge n ist der gültige Bereich a[0] bis a[n1].

Da der Zugriff auf die Variablen über einen Index erfolgt, werden diese Variablen auch indexierte Variablen genannt.

[zB]Beispiel

Greife auf das erste und letzte Zeichen aus dem Array zu:

char[] name = { 'C', 'h', 'r', 'i', 's' };

char first = name[ 0 ]; // C

char last = name[ name.length1 ]; // s

[zB]Beispiel

Laufe das Array der ersten Primzahlen komplett ab:

int[] primes = { 2, 3, 5, 7, 11 };

for ( int i = 0; i < primes.length; i++ ) // Index: 0 <= i < 5 = primes.length

System.out.println( primes[ i ] );

Anstatt ein Array einfach nur so abzulaufen und die Werte auszugeben, soll unser nächstes Programm den Mittelwert einer Zahlenfolge berechnen und ausgeben:

Listing 3.17PrintTheAverage.java

public class PrintTheAverage {



public static void main( String[] args ) {

double[] numbers = { 1.9, 7.8, 2.4, 9.3 };



double sum = 0;



for ( int i = 0; i < numbers.length; i++ )

sum += numbers[ i ];



double avg = sum / numbers.length;



System.out.println( avg ); // 5.35

}

}

Das Array muss mindestens ein Element besitzen, sonst gibt es bei der Division durch 0 eine Ausnahme.

Über den Typ des Index *

Innerhalb der eckigen Klammern steht ein positiver Ganzzahl-Ausdruck vom Typ int, der sich zur Laufzeit berechnen lassen muss. long-Werte, boolean, Gleitkommazahlen oder Referenzen sind nicht möglich; durch int verbleiben aber mehr als zwei Milliarden Elemente. Bei Gleitkommazahlen bliebe die Frage nach der Zugriffstechnik. Hier müssten wir den Wert auf ein Intervall herunterrechnen.

Strings sind keine Arrays *

Ein Array von char-Zeichen hat einen ganz anderen Typ als ein String-Objekt. Während bei Arrays eckige Klammern erlaubt sind, bietet die String-Klasse keinen Zugriff auf Zeichen über []. Die Klasse String bietet jedoch einen Konstruktor an, sodass aus einem Array mit Zeichen ein String-Objekt erzeugt werden kann. Alle Zeichen des Arrays werden kopiert, sodass anschließend Array und String keine Verbindung mehr besitzen. Dies bedeutet: Wenn sich das Array ändert, ändert sich der String nicht automatisch mit. Das kann er auch nicht, da Strings unveränderlich sind.

 

Zum Seitenanfang

3.8.6Array-Objekte mit new erzeugen Zur vorigen ÜberschriftZur nächsten Überschrift

Ein Array muss mit dem Schlüsselwort new unter Angabe einer festen Größe erzeugt werden, da Arrays Objekt sind. Das Anlegen der Referenzvariablen allein erzeugt noch kein Array mit einer bestimmten Länge. In Java ist das Anlegen des Arrays genauso dynamisch wie die Objekterzeugung. Dies drückt auch das new aus.[ 118 ](Programmiersprachen wie C(++) bieten bei der Felderzeugung Abkürzungen wie int array[100]. Das führt in Java zu einem Compilerfehler. ) Die Länge des Arrays wird in eckigen Klammern angegeben. Hier kann ein beliebiger Integer-Wert stehen, auch eine Variable. Selbst 0 ist möglich.

[zB]Beispiel

Erzeuge ein Array für zehn Elemente:

int[] values;

values = new int[ 10 ];

Die Array-Deklaration ist auch zusammen mit der Initialisierung möglich:

double[] values = new double[ 10 ];

Die Arrays mit primitiven Werten sind mit 0, 0.0 oder false und bei Verweisen mit null initialisiert.

Dass Arrays Objekte sind, zeigen einige Indizien:

  • Eine spezielle Form der new-Schreibweise erzeugt ein Exemplar der Array-Klasse; new erinnert uns immer daran, dass ein Objekt zur Laufzeit aufgebaut wird.

  • Ein Array-Objekt kennt das Attribut length, und auf dem Array-Objekt sind Methoden – wie clone() und alles, was java.lang.Object hat – definiert.

  • Die Operatoren == und != haben ihre Objektbedeutung: Sie vergleichen lediglich, ob zwei Variablen auf das gleiche Array-Objekt verweisen, aber auf keinen Fall die Inhalte der Arrays (das kann aber Arrays.equals(…)).

Der Zugriff auf die Array-Elemente über die eckigen Klammern [] lässt sich als versteckter Aufruf über geheime Methoden wie array.get(index) verstehen. Der []-Operator wird bei anderen Objekten nicht angeboten.

[»]Hinweis

Der Index eines Arrays muss von einem Typ sein, der ohne Verlust in int konvertierbar ist. Dazu gehören byte, short und char. Günstig ist ein Index vom Typ char, zum Beispiel als Laufvariable, wenn Array von Zeichen generiert werden:

char[] alphabet = new char[ 'z' – 'a' + 1 ]; // 'a' entspricht 97 und 'z' 122

for ( char c = 'a'; c <= 'z'; c++ )

alphabet[ c – 'a' ] = c; // alphabet[0]='a', alphabet[1]='b', usw.

Genau genommen haben wir es auch hier mit Indexwerten vom Typ int zu tun, weil mit den char-Werten vorher noch gerechnet wird.

 

Zum Seitenanfang

3.8.7Typische Array-Fehler Zur vorigen ÜberschriftZur nächsten Überschrift

Beim Zugriff auf ein Array-Element können Fehler auftreten. Zunächst einmal kann das Array-Objekt fehlen, sodass die Referenzierung fehlschlägt.

[zB]Beispiel

Der Compiler bemerkt den folgenden Fehler nicht, und die Strafe ist eine NullPointerException zur Laufzeit:[ 119 ](Obwohl er sich bei nicht initialisierten lokalen Variablen auch beschwert. )

int[] array = null;

array[ 1 ] = 1; // inline NullPointerException

Weitere Fehler können im Index begründet sein. Ist der Index negativ[ 120 ](Ganz anders verhalten sich da Python oder Perl. Dort wird ein negativer Index dazu verwendet, ein Feldelement relativ zum letzten Array-Eintrag anzusprechen. Und auch bei C ist ein negativer Index durchaus möglich und praktisch. ) oder zu groß, dann gibt es eine IndexOutOfBoundsException. Jeder Zugriff auf das Array wird zur Laufzeit getestet, auch wenn der Compiler durchaus einige Fehler finden könnte.

[zB]Beispiel

Bei folgenden Zugriffen könnte der Compiler theoretisch Alarm schlagen, was aber zumindest der Standard-Compiler nicht tut. Der Grund ist, dass der Zugriff auf die Elemente auch mit einem ungültigen Index syntaktisch völlig in Ordnung ist.

int[] array = new int[ 100 ];

array[ –10 ] = 1; // inline Fehler zur Laufzeit, nicht zur Compilezeit

array[ 100 ] = 1; // inline Fehler zur Laufzeit, nicht zur Compilezeit

Wird die IndexOutOfBoundsException nicht abgefangen, bricht das Laufzeitsystem das Programm mit einer Fehlermeldung ab. Dass die Array-Grenzen überprüft werden, ist Teil von Javas Sicherheitskonzept 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 Array-Zugriff korrekt ist.

Spielerei: Der Index und das Inkrement *

Wir haben beim Inkrement schon ein Phänomen wie i = i++ betrachtet. Ebenso ist auch die Anweisung bei einem Array-Zugriff zu behandeln:

array[ i ] = i++;

Bei der Position array[i] wird i gesichert und anschließend die Zuweisung vorgenommen. Wenn wir eine Schleife darum konstruieren, erweitern wir dies zu einer Initialisierung:

int[] array = new int[ 4 ];

int i = 0;

while ( i < array.length )

array[ i ] = i++;

Die Ausgabe ergibt 0, 1, 2 und 3. Von der Anwendung ist wegen mangelnder Übersicht abzuraten.

 

Zum Seitenanfang

3.8.8Arrays als Methodenparameter Zur vorigen ÜberschriftZur nächsten Überschrift

Verweise auf Arrays lassen sich bei Methoden genauso übergeben wie Verweise auf ganz normale Objekte. In der Deklaration heißt es dann zum Beispiel foo(int[] val) statt foo(String val).

Wir hatten vorher schon den Mittelwert einer Zahlenreihe ermittelt. Die Logik dafür ist perfekt in eine Methode ausgelagert:

Listing 3.18Avg1.java

public class Avg1 {



static double avg( double[] array ) {

double sum = 0;



for ( int i = 0; i < array.length; i++ )

sum += array[ i ];



return sum / array.length;

}



public static void main( String[] args ) {

double[] numbers = { 2, 3, 4 };

System.out.println( avg( numbers ) ); // 3.0

}

}

null-Referenzen prüfen

Referenzen bringen immer das Problem mit sich, dass sie null sein können. Syntaktisch gültig ist ein Aufruf von avg(null). Daher sollte eine Implementierung auf null testen und ein falsches Argument melden, etwa so:

if ( array == null || array.length == 0 )

throw new IllegalArgumentException( "Array null oder leer" );

Zu den Details siehe Kapitel 7, »Ausnahmen müssen sein«.

 

Zum Seitenanfang

3.8.9Vorinitialisierte Arrays Zur vorigen ÜberschriftZur nächsten Überschrift

Wenn wir in Java ein Array-Objekt erzeugen und gleich mit Werten initialisieren wollen, dann schreiben wir etwa:

int[] primes = { 2, 3, 5, 7, 11, 13 };

Sollen die Array-Inhalte erst nach der Variablendeklaration initialisiert oder soll das Array auch ohne Variable genutzt werden, so erlaubt Java dies nicht:

primes = { 2, 5, 7, 11, 13 }; // inline Compilerfehler

avg( { 1.23, 4.94, 9.33, 3.91, 6.34 } ); // inline Compilerfehler

Ein Versuch wie dieser schlägt mit der Compilermeldung »Array constants can only be used in initializers« fehl.

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

int[] primes;

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

primes = tmpprimes;

Als zweiten Ansatz gibt es eine Variante der new-Schreibweise, die durch ein Paar eckiger Klammern erweitert wird. Es folgen in geschweiften Klammern die Initialwerte des Arrays. Die Größe des Arrays entspricht genau der Anzahl der Werte. Für die oberen Beispiele ergibt sich folgende Schreibweise:

int[] primes;

primes = new int[]{ 2, 5, 7, 11, 13 };

Diese Notation ist auch bei Methodenaufrufen sehr praktisch, wenn Arrays übergeben werden:

avg( new double[]{ 1.23, 4.94, 9.33, 3.91, 6.34 } );

Da hier ein initialisiertes Array mit Werten gleich an die Methode übergeben und keine zusätzliche Variable benutzt wird, heißt diese Art der Arrays anonyme Arrays. Eigentlich gibt es auch sonst anonyme Arrays, wie new int[2000].length zeigt, doch wird in diesem Fall das Array nicht mit Werten initialisiert.

 

Zum Seitenanfang

3.8.10Die erweiterte for-Schleife Zur vorigen ÜberschriftZur nächsten Überschrift

for-Schleifen laufen oft Felder oder Datenstrukturen ab. Bei der Berechnung des Mittelwerts konnten wir das ablesen:

double sum = 0;

for ( int i = 0; i < array.length; i++ )

sum += array[ i ];

double arg = sum / array.length;

Die Schleifenvariable i hat lediglich als Index ihre Berechtigung; nur damit lässt sich das Element an einer bestimmten Stelle im Array ansprechen.

Weil das komplette Durchlaufen von Arrays häufig ist, gibt es eine Abkürzung für solche Iterationen:

for ( Typ Bezeichner : Array )

Die erweiterte Form der for-Schleife löst sich vom Index und erfragt jedes Element des Arrays. Das können Sie sich als Durchlauf einer Menge vorstellen, denn der Doppelpunkt liest sich als »in«. Rechts vom Doppelpunkt steht immer ein Array oder, wie wir später sehen werden, etwas vom Typ Iterable, wie eine Datenstruktur. Links wird eine neue lokale Variable deklariert, die später beim Ablauf jedes Element der Sammlung annehmen wird.

Die Berechnung des Durchschnitts lässt sich nun umschreiben. Die statische Methode avg(…) soll mit dem erweiterten for über die Schleife laufen, anstatt den Index selbst hochzuzählen. Eine Ausnahme zeigt an, ob der Array-Verweis null ist oder das Array keine Elemente enthält:

Listing 3.19Avg2.java, avg()

static double avg( double[] array ) {

if ( array == null || array.length == 0 )

throw new IllegalArgumentException( "Array null oder leer" );



double sum = 0;



for ( double n : array )

sum += n;



return sum / array.length;

}

Zu lesen ist die for-Zeile demzufolge als »Für jedes Element n vom Typ double in array tue …«. Eine Variable für den Schleifenindex ist nicht mehr nötig.

Anonyme Arrays in der erweiterten for-Schleife nutzen

Rechts vom Doppelpunkt lässt sich auf die Schnelle ein Array aufbauen, über welches das erweiterte for dann laufen kann:

for ( int prime : new int[]{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 } )

System.out.println( prime );

Das ist praktisch, um über eine feste Menge von Werten zu laufen. Das funktioniert auch für Objekte, etwa Strings:

for ( String name : new String[]{ "Cherry", "Gracel", "Fe" } )

System.out.println( name );

Einige Programmierer verstecken die Objekterzeugung auch in einem Methodenaufruf:

for ( String name : Arrays.asList( "Cherry ", "Gracel", "Fe" ) )

System.out.println( name );

[»]Hinweis

Arrays.asList(…) erzeugt kein Array als Rückgabe, sondern baut aus der variablen Argumentliste eine Sammlung auf, die von einem speziellen Typ Iterable ist – das kann auch die erweiterte for-Schleife ablaufen. Wir kommen später noch einmal darauf zu sprechen. Unabhängig vom erweiterten for hat die Nutzung von Arrays.asList(…)noch einen anderen Vorteil, etwa bei Ist-Element-von-Anfragen, etwa so:

if ( Arrays.asList( 1, 2, 6, 7, 8, 10 ).contains( number ) )

Mehr zu der Methode gleich in Abschnitt 3.8.19, »Die Klasse Arrays zum Vergleichen, Füllen, Suchen, Sortieren nutzen«.

Umsetzung und Einschränkung

Intern setzt der Compiler diese erweiterte for-Schleife ganz klassisch um, sodass der Bytecode unter beiden Varianten gleich ist. Nachteile dieser Variante sind jedoch:

  • Das erweiterte for läuft immer das ganze Array ab. Ein Anfang- und ein Ende-Index können nicht ausdrücklich gesetzt werden.

  • Die Ordnung ist immer von vorn nach hinten.

  • Die Schrittlänger ist immer eins.

  • Der Index ist nicht sichtbar.

  • Die Schleife liefert ein Element, kann aber nicht in das Array schreiben.

Abbrechen lässt sich die Schleife mit einem break. Bestehen andere Anforderungen, kann weiterhin nur eine klassische for-Schleife helfen.

 

Zum Seitenanfang

3.8.11Arrays mit nichtprimitiven Elementen Zur vorigen ÜberschriftZur nächsten Überschrift

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 Arrays, multipliziert mit dem Speicherbedarf einer Referenzvariablen. 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.

Arrays mit Strings durchsuchen

In unserem ersten Beispiel soll ein nichtprimitives Array Strings referenzieren und später schauen, ob eine Benutzereingabe im Array ist. String-Vergleiche lassen sich mit equals(…) realisieren:

Listing 3.20UserInputInStringArray.java

import java.util.Scanner;



public class UserInputInStringArray {



public static void main( String[] args ) {

String[] validInputs = { "Banane", "Apfel", "Kirsche" };



userInputLoop:

while ( true ) {

String input = new Scanner( System.in ).nextLine();



for ( String s : validInputs )

if ( s.equals( input ) )

break userInputLoop;

}



System.out.println( "Gültiges Früchtchen eingegeben" );

}

}

Zur Initialisierung des Arrays nutzt das Programm eine kompakte Variante, die drei Dinge vereint: den Aufbau eines Array-Objekts (mit Platz für drei Referenzen), die Initialisierung des Array-Objekts mit den drei Objektreferenzen und schlussendlich die Initialisierung der Variablen validInputs mit dem neuen Array – alles in einer Anweisung.

Für die Suche kommt das erweiterte for zum Einsatz, das in eine Endlosschleife eingebettet ist. Erst wenn es einen Fund gibt, verlässt das break die Endlosschleife. Wir müssen hier zu Sprungmarken greifen, denn ein break ohne Sprungmarke würde die erweiterte for-Schleife beenden, was wir aber nicht möchten.

Zufällige Spielerpositionen erzeugen

Im zweiten Beispiel sollen fünf zufällig initialisierte Punkte in einem Array abgelegt werden. Die Punkte sollen Spieler repräsentieren.

Zunächst benötigen wir ein Array:

Point[] players = new Point[ 5 ];

Die Deklaration schafft Platz für fünf Verweise auf Punkt-Objekte, aber kein einziges Point-Objekt ist angelegt. Standardmäßig werden die Array-Elemente mit der null-Referenz initialisiert, sodass System.out.println(players[0]) die Ausgabe »null« auf den Bildschirm bringen würde. Bei null wollen wir es nicht belassen, daher müssen die einzelnen Array-Plätze etwa mit players[0] = new Point() initialisiert werden.

Zufallszahlen erzeugt die mathematische Methode Math.random(). Da die statische Methode jedoch Fließkommazahlen zwischen 0 (inklusiv) und 1 (exklusiv) liefert, werden die Zahlen zunächst durch Multiplikation frisiert und dann abgeschnitten.

Im letzten Schritt geben wir ein Raster auf dem Bildschirm aus, in dem zwei ineinander verschachtelte Schleifen alle x/y-Koordinaten des gewählten Bereichs ablaufen und dann ein »&« setzen, wenn der Punkt einen Spieler trifft.

Das Programm als Ganzes:

Listing 3.21FivePlayers.java

import java.awt.Point;

import java.util.Arrays;



public class FivePlayers {



public static void main( String[] args ) {

Point[] players = new Point[ 5 ];



for ( int i = 0; i < players.length; i++ )

players[ i ] = new Point( (int)(Math.random() * 40),

(int)(Math.random() * 10) );



for ( int y = 0; y < 10; y++ ) {

for ( int x = 0; x < 40; x++ )

if ( Arrays.asList( players ).contains( new Point(x, y) ) )

System.out.print( "&" );

else

System.out.print( "." );

System.out.println();

}

}

}

Der Ausdruck Arrays.asList(players).contains(new Point(x, y)) testet, ob irgendein Punkt im Array players gleich dem Punkt mit den x/y-Koordinaten ist.

Die Ausgabe erzeugt zum Beispiel Folgendes:

........................................

...............&........................

&.......................................

........................................

........................................

..............................&.........

........................................

...&....................&...............

........................................

........................................

Während die erweiterte for-Schleife gut das Array ablaufen kann, funktioniert das zur Initialisierung nicht, denn das erweiterte for ist nur zum Lesen gut. Elementinitialisierungen funktionieren bei Arrays nur mit players[i]=..., und dazu ist eben eine klassische for-Schleife mit dem Index nötig.

 

Zum Seitenanfang

3.8.12Methode mit variabler Argumentanzahl (Vararg) Zur vorigen ÜberschriftZur nächsten Überschrift

Bei vielen Methoden ist es klar, wie viele Argumente sie haben; eine Sinus-Methode bekommt nur ein Argument, equals(Object) ein Objekt, println(…) nichts oder genau ein Argument usw. Es gibt jedoch Methoden, bei denen die Zahl mehr oder weniger frei ist. Ein paar Beispiele:

  • Wenn der Aufruf System.out.printf(formatierungsstring, arg1, args2, arg3, …) etwas auf dem Bildschirm ausgibt, ist erst einmal nicht bekannt, wie viele Argumente die Methode besitzt, denn sie sind abhängig vom Formatierungsstring.

  • Fügt Collections.addAll(sammlung, elem1, elem2, elem3, …) etwas einer Sammlung hinzu, ist frei, wie viele Elemente es sind.

  • Wird ein Pfad für das Dateisystem zusammengebaut, ist vorher unbekannt, wie viele Segmente Paths.get("ordner1", "ordner2", "ordner3", …) besitzt.

  • Startet new ProcessBuilder("kommando", "arg1", "arg2", …).start() ein Hintergrundprogramm, ist der Methode unbekannt, wie viele Argumente dem externen Programm übergeben werden.

Um die Anzahl der Parameter beliebig zu gestalten, sieht Java Methoden mit variabler Argumentanzahl vor, Varargs genannt – in anderen Programmiersprachen heißen sie Variadische Funktion.

System.out.printf(…) nimmt eine beliebige Anzahl von Argumenten an

Eine Methode mit Varargs haben wir schon einige Male verwendet: printf(…). Die Deklaration ist wie folgt:

class java.io.PrintStream extends FilterOutputStream

implements Appendable, Closeable
  • PrintStream printf(String format, Object... args)

    Nimmt eine beliebige Liste von Argumenten an und formatiert sie nach dem gegebenen Formatierungsstring format.

Eine Methode mit variabler Argumentanzahl nutzt die Ellipse (...) zur Verdeutlichung, dass eine beliebige Anzahl Argumente angegeben werden darf, dazu zählt auch die Angabe keines Elements. Der Typ fällt dabei aber nicht unter den Tisch; er wird ebenfalls angegeben.

Gültige Aufrufe von printf(…) sind demnach:

Aufruf

Variable Argumentliste

System.out.printf("%n")

Ist leer.

System.out.printf("%s", "Eins")

Besteht aus nur einem Element: "Eins".

System.out.printf("%s,%s,%s", "1", "2", "3")

Besteht aus drei Elementen: "1", "2", "3".

Tabelle 3.5Gültige Aufrufe von printf(…)

Durchschnitt finden von variablen Argumenten

Wir haben vorher eine Methode avg(double[] array) geschrieben, die den arithmetischen Mittelwert von Werten berechnet. Den Parametertyp können wir nun ändern in avg(double... array), sodass die Methode einfach mit variablen Argumenten gefüllt werden kann.

Ausprogrammiert sieht das so aus; es gib nur eine kleine Änderung von [] in ..., sonst ändert sich an der Implementierung nichts.

Listing 3.22AvgVarArgs.java

public class AvgVarArgs {



static double avg( double... array ) { /* wie vorher */ }



public static void main( String[] args ) {

System.out.println( avg(1, 2, 9, 3) ); // 3.75

}

}

[»]Hinweis

Werden variable Argumentlisten in der Signatur definiert, so dürfen sie nur den letzten Parameter bilden; andernfalls könnte der Compiler bei den Parametern nicht unbedingt zuordnen, was nun ein Vararg und was schon der nächste gefüllte Parameter ist. Das bedingt automatisch, dass es nur maximal ein Vararg in der Parameterliste geben kann.

Zusammenhang Vararg und Array

Eine Methode mit Vararg ist im Kern eine Methode mit einem Parametertyp Array. Im Bytecode steht nicht wirklich avg(double... array), sondern avg(double[] array) mit der Zusatzinfo, dass array ein Vararg ist, damit der Compiler beliebig viele Argumente und nicht ausschließlich ein double[]-Array als Argument erlaubt.

Der Nutzer kann eine Varargs-Methode aufrufen, ohne ein Array für die Argumente explizit zu definieren. Er bekommt auch gar nicht mit, dass der Compiler im Hintergrund ein Array mit vier Elementen angelegt hat. So übergibt der Compiler:

System.out.println( avg( new double[] { 1, 2, 9, 3 } ) );

An der Schreibweise lässt sich gut ablesen, dass wir ein Array auch von Hand übergeben können:

double[] values = { 1, 2, 9, 3 };

System.out.println( avg( values ) );

[»]Hinweis

Da Varargs als Arrays umgesetzt werden, sind überladene Varianten wie avg(int... array) und avg(int[] array), also einmal mit einem Vararg und einmal mit einem Array, nicht möglich. Besser ist es hier, immer eine Variante mit Varargs zu nehmen, da diese mächtiger ist. Einige Autoren schreiben auch die Einstiegsmethode main(String[] args) mit variablen Argumenten, also main(String… args). Das ist gültig, denn im Bytecode steht ja ein Array.

Varargs-Designtipps *

  • Hat eine Methode nur einen Array-Parameter und steht er noch am Ende, so kann er relativ einfach durch ein Vararg ersetzt werden. Das gibt dem Aufrufer die komfortable Möglichkeit, eine kompaktere Syntax zu nutzen. Unsere main(String[] args)-Methode kann auch als main(String... args) deklariert werden, sodass der main(…)-Methode bei Tests einfach variable Argumente übergeben werden können.

  • Muss eine Mindestanzahl von Argumenten garantiert werden – bei max() sollten das mindestens zwei sein –, ist es besser, eine Deklaration wie folgt zu nutzen: max(int first, int second, int... remaining).

  • Aus Performance-Gründen ist es nicht schlecht, Methoden mit häufigen Parameterlistengrößen als feste Methoden anzubieten, etwa max(double, double), max(double, double, double) und dann max(double...). Der Compiler wählt automatisch immer die passende Methode aus, für zwei oder drei Parameter sind keine temporären Array-Objekte nötig, und die automatische Speicherbereinigung muss nichts wegräumen.

 

Zum Seitenanfang

3.8.13Mehrdimensionale Arrays * Zur vorigen ÜberschriftZur nächsten Überschrift

Java realisiert mehrdimensionale Arrays durch Arrays von Arrays. Sie können etwa für die Darstellung von mathematischen Matrizen oder Rasterbildern Verwendung finden. Dieser Abschnitt lehrt, wie Objekte für mehrdimensionale Arrays initialisiert, aufgebaut und abgegrast werden.

Mehrdimensionale Array-Objekte mit new aufbauen

Die folgende Zeile deklariert ein zweidimensionales Array mit Platz für insgesamt 32 Zellen, die in vier Zeilen und acht Spalten angeordnet sind:

int[][] A = new int[ 4 ][ 8 ];

Obwohl mehrdimensionale Arrays im Prinzip Arrays mit Arrays als Elementen sind, lassen sie sich leicht deklarieren.

[+]Tipp

Zwei alternative Deklarationen (die Position der eckigen Klammern ist verschoben) sind:

int A[][] = new int[ 4 ][ 8 ];

int[] A[] = new int[ 4 ][ 8 ];

Es ist zu empfehlen, alle eckigen Klammen hinter den Typ zu setzen.

Anlegen und Initialisieren in einem Schritt

Ebenso wie bei eindimensionalen Arrays lassen sich mehrdimensionale Arrays gleich beim Anlegen initialisieren:

int[][] A3x2 = { {1, 2}, {2, 3}, {3, 4} };

int[][] B = { {1, 2}, {2, 3, 4}, {5} };

Der zweite Fall lässt erkennen, dass das Array nicht unbedingt rechteckig sein muss. Dazu gleich mehr.

Zugriff auf Elemente

Einzelne Elemente spricht der Ausdruck A[i][j] an.[ 121 ](Die in Pascal übliche Notation A[i,j] wird in Java nicht unterstützt. Die Notation wäre im Prinzip möglich, da Java im Gegensatz zu C(++) den Komma-Operator nur in for-Schleifen zulässt. ) Der Zugriff erfolgt mit so vielen Klammerpaaren, wie die Dimension des Arrays angibt.

[zB]Beispiel

Der Aufbau von zweidimensionalen Arrays (und der Zugriff auf sie) ist mit einer Matrix bzw. Tabelle vergleichbar. 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]

...

length bei mehrdimensionalen Arrays

Nehmen wir eine Buchstabendefinition wie die folgende:

char[][] letter = { { ' ', '#', ' ' },

{ '#', ' ', '#' },

{ '#', ' ', '#' },

{ '#', ' ', '#' },

{ ' ', '#', ' ' } };

Dann können wir length auf zwei verschiedene Weisen anwenden:

  • letter.length ergibt 5, denn es gibt fünf Zeilen.

  • letter[0].length ergibt 3 – genauso wie letter[1].length usw. –, weil jedes Unter-Array die Größe 3 hat.

Zweidimensionale Arrays mit ineinander verschachtelten Schleifen ablaufen

Um den Buchstaben unseres Beispiels auf dem Bildschirm auszugeben, nutzen wir zwei ineinander verschachtelte Schleifen:

for ( int line = 0; line < letter.length; line++ ) {

for ( int column = 0; column < letter[line].length; column++ )

System.out.print( letter[line][column] );

System.out.println();

}

Fassen wir das Wissen zu einem Programm zusammen, das vom Benutzer eine Zahl erfragt und diese Zahl in Binärdarstellung ausgibt. Wir drehen die Buchstaben um 90 Grad im Uhrzeigersinn, damit wir uns nicht damit beschäftigen müssen, die Buchstaben horizontal nebeneinanderzulegen.

Listing 3.23BinaryBanner.java

import java.util.Scanner;



public class BinaryBanner {



static void printLetter( char[][] letter ) {

for ( int column = 0; column < letter[0].length; column++ ) {

for ( int line = letter.length – 1; line >= 0; line-- )

System.out.print( letter[line][column] );

System.out.println();

}

System.out.println();

}



static void printZero() {

char[][] zero = { { ' ', '#', ' ' },

{ '#', ' ', '#' },

{ '#', ' ', '#' },

{ '#', ' ', '#' },

{ ' ', '#', ' ' } };

printLetter( zero );

}



static void printOne() {

char[][] one = { { ' ', '#' },

{ '#', '#' },

{ ' ', '#' },

{ ' ', '#' },

{ ' ', '#' } };

printLetter( one );

}



public static void main( String[] args ) {

int input = new Scanner( System.in ).nextInt();

String bin = Integer.toBinaryString( input );

System.out.printf( "Banner für %s (binär %s):%n", input, bin );

for ( int i = 0; i < bin.length(); i++ )

switch ( bin.charAt( i ) ) {

case '0': printZero(); break;

case '1': printOne(); break;

}

}

}

Die Methode printLetter(char[][]) bekommt als Argument das zweidimensionale Array und läuft anders ab als im ersten Fall, um die Rotation zu realisieren. Mit der Eingabe »2« gibt es folgende Ausgabe:

Banner für 2 (binär 10):

#

#####



###

# #

###
 

Zum Seitenanfang

3.8.14Nichtrechteckige Arrays * Zur vorigen ÜberschriftZur nächsten Überschrift

Da in Java mehrdimensionale Arrays als Arrays von Arrays implementiert sind, müssen diese nicht zwingend rechteckig sein. Jede Zeile im Array kann eine eigene Größe haben.

[zB]Beispiel

Ein dreieckiges Array mit Zeilen der Länge 1, 2 und 3:

int[][] a = new int[ 3 ][];

for ( int i = 0; i < 3; i++ )

a[ i ] = new int[ i + 1 ];

Initialisierung der Unter-Arrays

Wenn wir ein mehrdimensionales Array deklarieren, erzeugen versteckte Schleifen automatisch die inneren Arrays. Bei

int[][] a = new int[ 3 ][ 4 ];

erzeugt die Laufzeitumgebung die passenden Unter-Arrays automatisch. Dies ist bei

int[][] a = new int[ 3 ][];

nicht so. Hier müssen wir selbst die Unter-Arrays initialisieren, bevor wir auf die Elemente zugreifen:

for ( int i = 0; i < a.length; i++ )

a[ i ] = new int[ 4 ];

PS: int[][] m = new int[][4]; funktioniert natürlich nicht!

[zB]Beispiel

Es gibt verschiedene Möglichkeiten, ein mehrdimensionales Array zu initialisieren:

int[][] A3x2 = { {1,2}, {2,3}, {3,4} };

oder

int[][] A3x2 = new int[][]{ {1,2}, {2,3}, {3,4} };

oder

int[][] A3x2 = new int[][]{ new int[]{1,2}, new int[]{2,3}, new int[]{3,4} };

Das pascalsche Dreieck

Das folgende Beispiel zeigt eine weitere Anwendung von nichtrechteckigen Arrays, in der das pascalsche Dreieck nachgebildet wird. Das Dreieck ist so aufgebaut, dass die Elemente unter einer Zahl genau die Summe der beiden direkt darüberstehenden Zahlen bilden. Die Ränder sind mit Einsen belegt.

Listing 3.24Das pascalsche Dreieck

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

In der Implementierung wird zu jeder Ebene dynamisch ein Array mit der passenden Länge angefordert. Die Ausgabe tätigt printf(…) mit einigen Tricks mit dem Formatspezifizierer, da wir auf diese Weise ein führendes Leerzeichen bekommen:

Listing 3.25PascalsTriangle.java

class PascalsTriangle {



public static void main( String[] args ) {

int[][] triangle = new int[7][];



for ( int row = 0; row < triangle.length; row++ ) {

System.out.print( new String( new char[(14 - row * 2)] ).replace( '\0', ' ' ) );



triangle[row] = new int[row + 1];



for ( int col = 0; col <= row; col++ ) {

if ( (col == 0) || (col == row) )

triangle[row][col] = 1;

else

triangle[row][col] = triangle[row – 1][col – 1] +

triangle[row – 1][col];



System.out.printf( "%3d ", triangle[row][col] );

}



System.out.println();

}

}

}

Die Anweisung new String( new char[(14 - row * 2)] ).replace( '\0', ' ' ) produziert Einrückungen und greift eine fortgeschrittene API auf. (14 - row * 2) ist die Größe des standardmäßig mit null initialisierten Arrays, das dann in den Konstruktor der Klasse String übergeben wird, das wiederum ein String-Objekt aus dem char-Array aufbaut. Die replace(…)-Methode auf dem frischen String-Objekt führt wieder zu einem neuen String-Objekt, in dem alle '\0' -Werte durch Leerzeichen ersetzt sind.

Andere Anwendungen

Mit zweidimensionalen Arrays ist die Verwaltung von symmetrischen Matrizen einfach, da eine solche Matrix symmetrisch zur Diagonalen gleiche Elemente enthält. Daher kann entweder die obere oder die untere Dreiecksmatrix entfallen. Besonders nützlich ist der Einsatz dieser effizienten Speicherform für Adjazenzmatrizen[ 122 ](Eine Adjazenzmatrix stellt eine einfache Art dar, Graphen zu speichern. Sie besteht aus einem zweidimensionalen Array, das die Informationen über vorhandene Kanten im (gerichteten) Graphen enthält. Existiert eine Kante von einem Knoten zum anderen, so befindet sich in der Zelle ein Eintrag: entweder true/false für »Ja, die beiden sind verbunden« oder ein Ganzzahlwert für eine Gewichtung (Kantengewicht). ) bei ungerichteten Graphen.

 

Zum Seitenanfang

3.8.15Die Wahrheit über die Array-Initialisierung * Zur vorigen ÜberschriftZur nächsten Überschrift

So schön die kompakte Initialisierung der Array-Elemente ist, so laufzeit- und speicherintensiv ist sie auch. Da Java eine dynamische Sprache ist, passt das Konzept der Array-Initialisierung nicht ganz in das Bild. Daher wird die Initialisierung auch erst zur Laufzeit durchgeführt. Unser Primzahl-Array

int[] primes = { 2, 3, 5, 7, 11, 13 };

wird vom Java-Compiler umgeformt und analog zu Folgendem behandelt:

int[] primes = new int[ 6 ];

primes[ 0 ] = 2;

primes[ 1 ] = 3;

primes[ 2 ] = 5;

primes[ 3 ] = 7;

primes[ 4 ] = 11;

primes[ 5 ] = 13;

Erst nach kurzem Überlegen wird das Ausmaß der Umsetzung sichtbar: Zunächst ist es der Speicherbedarf für die Methoden. Ist das Array primes in einer Methode deklariert und mit Werten initialisiert, kostet die Zuweisung Laufzeit, da wir viele Zugriffe haben, die auch alle schön durch die Indexüberprüfung gesichert sind. Da zudem der Bytecode für eine einzelne Methode wegen diverser Beschränkungen in der JVM nur beschränkt lang sein darf, kann dieser Platz für richtig große Arrays schnell erschöpft sein. Daher ist davon abzuraten, etwa Bilder oder große Tabellen im Programmcode zu speichern. Unter C war es populär, ein Programm einzusetzen, das eine Datei in eine Folge von Array-Deklarationen verwandelte. Ist dies in Java wirklich nötig, sollten wir Folgendes in Betracht ziehen:

  • Wir verwenden ein statisches Array (eine Klassenvariable), sodass das Array nur einmal während des Programmlaufs initialisiert werden muss.

  • Liegen die Werte im Byte-Bereich, können wir sie in einen String konvertieren und später den String in ein Array umwandeln. Das ist eine sehr clevere Methode, Binärdaten einfach unterzubringen.

 

Zum Seitenanfang

3.8.16Mehrere Rückgabewerte * Zur vorigen ÜberschriftZur nächsten Überschrift

Wenn wir in Java Methoden schreiben, dann haben sie über return höchstens einen Rückgabewert. Wollen wir aber mehr als einen Wert zurückgeben, müssen wir eine andere Lösung suchen. Zwei Ideen lassen sich verwirklichen:

  • Behälter wie Arrays oder andere Sammlungen fassen Werte zusammen und liefern sie als Rückgabe.

  • Spezielle Behälter werden übergeben, in denen die Methode Rückgabewerte platziert; eine return-Anweisung ist nicht mehr nötig.

Betrachten wir eine statische Methode, die für zwei Zahlen die Summe und das Produkt als Array liefert:

Listing 3.26MultipleReturnValues.java

public class MultipleReturnValues {



static int[] productAndSum( int a, int b ) {

return new int[]{ a * b, a + b };

}



public static void main( String[] args ) {

System.out.println( productAndSum(9, 3)[ 1 ] );

}

}

[»]Hinweis

Eine ungewöhnliche Syntax in Java erlaubt es, bei Array-Rückgaben das Paar eckiger Klammern auch hinter den Methodenkopf zu stellen, also statt

static int[] productAndSum( int a, int b )

alternativ Folgendes zu schreiben:

static int productAndSum( int a, int b )[]

Das ist nicht empfohlen. Selbst so etwas wie int[] transposeMatrix(int[][] m)[] ist möglich, wohl aber sinnvollerweise als int[][] transposeMatrix(int[][] matrix) geschrieben.

 

Zum Seitenanfang

3.8.17Klonen kann sich lohnen – Arrays vermehren * Zur vorigen ÜberschriftZur nächsten Überschrift

Wollen wir eine Kopie eines Arrays mit gleicher Größe und gleichem Elementtyp schaffen, so nutzen wir dazu die Objektmethode clone().[ 123 ](Das ist gültig, da Arrays intern die Schnittstelle Cloneable implementieren. System.out.println(new int[0] instanceof Cloneable); gibt true zurück. ) Sie klont – in unserem Fall kopiert – die Elemente des Array-Objekts in ein neues.

Listing 3.27CloneDemo.java, main(), Teil 1

int[] sourceArray = new int[ 6 ];

sourceArray[ 0 ] = 4711;

int[] targetArray = sourceArray.clone();

System.out.println( targetArray.length ); // 6

System.out.println( targetArray[ 0 ] ); // 4711

Im Fall von geklonten Objekt-Arrays ist es wichtig zu verstehen, dass die Kopie flach ist. Die Verweise aus dem ersten Array kopiert clone() in das neue Array, es klont aber die referenzierten Objekte selbst nicht. Bei mehrdimensionalen Arrays wird also nur die erste Dimension kopiert, Unter-Arrays werden somit gemeinsam genutzt:

Listing 3.28CloneDemo.java, main(), Teil 2

Point[] pointArray1 = { new Point(1, 2), new Point(2, 3) };

Point[] pointArray2 = pointArray1.clone();

System.out.println( pointArray1[ 0 ] == pointArray2[ 0 ] ); // true

Die letzte Zeile zeigt anschaulich, dass die beiden Arrays dasselbe Point-Objekt referenzieren; die Kopie ist flach, aber nicht tief.

 

Zum Seitenanfang

3.8.18Array-Inhalte kopieren * Zur vorigen ÜberschriftZur nächsten Überschrift

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

  • Auf zwei schon existierenden Arrays. Ein Teil eines Arrays wird in ein anderes Array kopiert. arraycopy(…) eignet sich dazu, sich vergrößernde Arrays zu implementieren, indem zunächst ein neues größeres Array angelegt wird und anschließend die alten Array-Inhalte in das neue Array kopiert werden.

  • Auf dem gleichen Array. So lässt sich die Methode dazu verwenden, Elemente eines Arrays um bestimmte Positionen zu verschieben. Die Bereiche können sich durchaus überlappen.

[zB]Beispiel

Um zu zeigen, dass arraycopy(…) auch innerhalb des eigenen Arrays kopiert, sollen alle Elemente bis auf eines im Array f nach links und nach rechts bewegt werden:

System.arraycopy( f, 1, f, 0, f.length – 1 ); // links

System.arraycopy( f, 0, f, 1, f.length – 1 ); // rechts

Hier bleibt jedoch ein Element doppelt!

final class java.lang.System
  • static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

    Kopiert length Einträge des Arrays src ab der Position srcPos in ein Array dest ab der Stelle destPos. Der Typ des Arrays ist egal, es muss nur in beiden Fällen der gleiche Typ sein. Die Methode arbeitet für große Arrays schneller als eine eigene Kopierschleife.

Kopieren der Elemente von einem Array in ein anderes

Abbildung 3.8Kopieren der Elemente von einem Array in ein anderes

 

Zum Seitenanfang

3.8.19Die Klasse Arrays zum Vergleichen, Füllen, Suchen, Sortieren nutzen Zur vorigen ÜberschriftZur nächsten Überschrift

Die Klasse java.util.Arrays deklariert nützliche statische Methoden für den Umgang mit Arrays. So bietet sie Möglichkeiten zum Vergleichen, Sortieren und Füllen von Arrays sowie zur binären Suche.

String-Repräsentation eines Arrays

Nehmen wir an, wir haben es mit einem Array von Hundenamen zu tun, das wir auf dem Bildschirm ausgeben wollen:

Listing 3.29DogArrayToString, main()

String[] dogs = {

"Flocky Fluke", "Frizzi Faro", "Fanny Favorit", "Frosty Filius",

"Face Flash", "Fame Friscco"

};

Soll der Array-Inhalt zum Testen auf den Bildschirm gebracht werden, so kommt eine Ausgabe mit System.out.println(dogs) nicht infrage, denn toString() ist auf dem Objekttyp Array nicht sinnvoll definiert:

System.out.println( dogs ); // [Ljava.lang.String;@10b62c9

Die statische Methode Arrays.toString(array) liefert für unterschiedliche Arrays die gewünschte String-Repräsentation des Arrays:

System.out.println( Arrays.toString(dogs) ); // [Flocky Fluke, ...]

Das spart nicht unbedingt eine for-Schleife, die durch das Array läuft und auf jedem Element printXXX(…) aufruft, denn die Ausgabe ist immer von einem bestimmten Format, das mit »[« beginnt, jedes Element mit Komma und einem Leerzeichen trennt und mit »]« abschließt.

Die Klasse Arrays deklariert die toString()-Methode für unterschiedliche Array-Typen:

class java.util.Arrays
  • static String toString(XXX[] a)

    Liefert eine String-Repräsentation des Arrays. Der Typ XXX steht stellvertretend für boolean, byte, char, short, int, long, float, double.

  • static String toString(Object[] a)

    Liefert eine String-Repräsentation des Arrays. Im Fall des Objekttyps ruft die Methode auf jedem Objekt im Array toString() auf.

  • static String deepToString(Object[] a)

    Ruft auch auf jedem Unter-Arrays Arrays.toString(…) auf und nicht nur toString() wie bei jedem anderen Objekt.

Sortieren

Diverse statische Arrays.sort(…)/Arrays.parallelSort(…)-Methoden ermöglichen das Sortieren von Elementen im Array. Bei primitiven Elementen (kein boolean) gibt es keine Probleme, da sie eine natürliche Ordnung haben.

[zB]Beispiel

Sortiere zwei Arrays:

int[] numbers = { –1, 3, –10, 9, 3 };

String[] names = { "Xanten", "Alpen", "Wesel" };

Arrays.sort( numbers );

Arrays.sort( names );

System.out.println( Arrays.toString( numbers ) ); // [-10, –1, 3, 3, 9]

System.out.println( Arrays.toString( names ) ); // [Alpen, Wesel, Xanten]

Besteht das Array aus Objektreferenzen, müssen die Objekte vergleichbar sein. Das gelingt entweder mit einem Extra-Comparator, oder die Klassen implementieren die Schnittstelle Comparable, wie zum Beispiel Strings. Kapitel 9, »Besondere Typen der Java SE«, beschreibt diese Möglichkeiten präzise.

class java.util.Arrays
  • static void sort(XXX[] a )

  • static void sort(XXX[] a, int fromIndex, int toIndex)

    Sortiert die gesamte Liste vom Typ XXX (wobei XXX für byte, char, short, int, long, float, double steht) oder einen ausgewählten Teil. Bei angegebenen Grenzen ist fromIndex wieder inklusiv und toIndex exklusiv. Sind die Grenzen fehlerhaft, löst die Methode eine Illegal-ArgumentException (im Fall fromIndex > toIndex) oder eine ArrayIndexOutOfBoundsException (fromIndex < 0 oder toIndex > a.length) aus.

  • static void sort(Object[] a)

  • static void sort(Object[] a, int fromIndex, int toIndex)

    Sortiert ein Array von Objekten. Die Elemente müssen Comparable implementieren.[ 124 ](Das stimmt nicht wirklich: Besteht das Array aus keinem oder nur einem Element, ist nichts zu vergleichen, also ist folglich auch nicht relevant, ob der Objekttyp Comparable ist. ) Bei der Methode gibt es keinen generischen Typparameter, der das zur Übersetzungszeit erzwingt!

  • static <T> void sort(T[] a, Comparator<? super T> c)

  • static <T> void sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c)

    Sortiert ein Array von Objekten mit gegebenem Comparator.

Paralleles Sortieren

Spezielle Sortiermethoden sind für sehr große Arrays gedacht. Bei den neuen parallelSort(…)-Methoden verwendet die Bibliothek mehrere Threads, um Teile parallel zu sortieren, was die Geschwindigkeit erhöhen kann. Eine Garantie ist das aber nicht, denn ein Performance-Vorteil ergibt sich wirklich nur bei großen Arrays.

class java.util.Arrays
  • static void parallelSort(byte[] a)

  • static void parallelSort(byte[] a, int fromIndex, int toIndex)

  • static void parallelSort(char[] a)

  • static void parallelSort(char[] a, int fromIndex, int toIndex)

  • static void parallelSort(short[] a)

  • static void parallelSort(short[] a, int fromIndex, int toIndex)

  • static void parallelSort(int[] a)

  • static void parallelSort(int[] a, int fromIndex, int toIndex)

  • static void parallelSort(long[] a)

  • static void parallelSort(long[] a, int fromIndex, int toIndex)

  • static void parallelSort(float[] a)

  • static void parallelSort(float[] a, int fromIndex, int toIndex)

  • static void parallelSort(double[] a)

  • static void parallelSort(double[] a, int fromIndex, int toIndex)

  • static <T extends Comparable<? super T>> void parallelSort(T[] a)

  • static <T extends Comparable<? super T>> void parallelSort(T[] a, int fromIndex,

    int toIndex)

  • static <T> void parallelSort(T[] a, Comparator<? super T> c)

  • static <T> void parallelSort(T[] a, int fromIndex, int toIndex,

    Comparator<? super T> c)

Der verwendete Algorithmus ist einfach zu verstehen: Zunächst wird das Array in Teil-Arrays partitioniert, diese werden parallel sortiert und dann zu einem größeren sortierten Array zusammengelegt. Das Verfahren nennt sich im Englischen auch parallel sort-merge.

Parallele Berechnung von Präfixen *

Stehen mehrere Prozessoren oder Kerne zur Verfügung, können bestimmte Berechnungen bei Arrays parallelisiert werden. Ein Algorithmus nennt sich parallele Präfix-Berechnung und basiert auf der Idee, dass eine assoziative Funktion – nennen wir sie f – auf eine bestimmte Art und Weise auf Elemente eines Arrays – nennen wir es a – angewendet wird, nämlich so:

  • a[0]

  • f(a[0], a[1])

  • f(a[0], f(a[1], a[2]))

  • f(a[0], f(a[1], f(a[2], a[3])))

  • f(a[0], f(a[1],f(a[n-2], a[n-1])…))

In der Aufzählung sieht das etwas verwirrend aus, daher soll ein praktisches Beispiel das Verständnis erleichtern. Das Array sei [1, 3, 0, 4] und die binäre Funktion die Addition.

Index

Funktion

Ergebnis

0

a[0]

1

1

a[0] + a[1]

1 + 3 = 4

2

a[0] + (a[1] + a[2])

1 + (3 + 0) = 4

3

a[0] + (a[1] + (a[2] + a[3]))

1 + (3 + (0 + 4)) = 8

Tabelle 3.6Präfix-Berechnung vom Array [1, 3, 0, 4] mit Additionsfunktion

Auf den ersten Blick wirkt das wenig spannend, doch kann der Algorithmus parallelisiert werden und somit im besten Fall in logarithmischer Zeit (mit n Prozessoren) gelöst werden. Voraussetzung dafür ist allerdings eine assoziative Funktion, wie Summe und Maximum. Ohne ins Detail zu gehen, könnten wir uns vorstellen, dass ein Prozessor/Kern 0 + 4 berechnet, ein anderer zeitgleich 1 + 3 und dass dann das Ergebnis zusammengezählt wird.

[zB]Beispiel *

Das Beispiel unserer Präfix-Berechnung mithilfe einer Methode aus Arrays:

int[] array = {1, 3, 0, 4};

Arrays.parallelPrefix( array, (a, b) -> a + b );

System.out.println( Arrays.toString( array ) ); // [1, 4, 4, 8]

Die Implementierung nutzt die fortgeschrittene Syntax der Lambda-Ausdrücke. Statt (a + b) ‐> a + b verkürzt es Integer::sum sogar noch.

Ein weiteres Beispiel: Finde das Maximum in einer Menge von Fließkommazahlen:

double[] array = {4.8, 12.4, -0.7, 3.8 };

Arrays.parallelPrefix( array, Double::max );

System.out.println( array[array.length -1 ] ); // 12.4

Das Beispiel nutzt schon die Methode, die Arrays für die parallele Präfix-Berechnung bietet:

class java.util.Arrays
  • static void parallelPrefix(int[] array, IntBinaryOperator op)

  • static void parallelPrefix(int[] array, int fromIndex, int toIndex,

    IntBinaryOperator op)

  • static void parallelPrefix(long[] array, LongBinaryOperator op)

  • static void parallelPrefix(long[] array, int fromIndex, int toIndex,

    LongBinaryOperator op)

  • static void parallelPrefix(double[] array, DoubleBinaryOperator op)

  • static void parallelPrefix(double[] array, int fromIndex, int toIndex,

    DoubleBinaryOperator op)

  • static <T> void parallelPrefix(T[] array, BinaryOperator<T> op)

  • static <T> void parallelPrefix(T[] array, int fromIndex, int toIndex,

    BinaryOperator<T> op)

Arrays mit Arrays.equals(…) und Arrays.deepEquals(…) vergleichen *

Die statische Methode Arrays.equals(…) vergleicht, ob zwei Arrays die gleichen Inhalte besitzen; dazu ist die überladene Methode für alle wichtigen Typen definiert. Wenn zwei Arrays tatsächlich die gleichen Inhalte besitzen, ist die Rückgabe der Methode true, sonst false. Natürlich müssen beide Arrays schon die gleiche Anzahl von Elementen besitzen, sonst ist der Test sofort vorbei und das Ergebnis false. Im Fall von Objekt-Arrays nutzt Arrays.equals(…) nicht die Identitätsprüfung per ==, sondern die Gleichheit per equals(…).

[zB]Beispiel

Vergleiche zwei Arrays:

int[] array1 = { 1, 2, 3, 4 };

int[] array2 = { 1, 2, 3, 4 };

System.out.println( Arrays.equals( array1, array2 ) ); // true

Ein Vergleich von Teil-Arrays ist leider auch nach mehr als zehn Jahren Java-Bibliothek einfach nicht vorgesehen.

Bei unterreferenzierten Arrays betrachtet Arrays.equals(…) das innere Array als einen Objektverweis und vergleicht es auch mit equals(…) – was jedoch bedeutet, dass nicht identische, aber mit gleichen Elementen referenzierte innere Arrays als ungleich betrachtet werden. Die statische Methode deepEquals(…) bezieht auch unterreferenzierte Arrays in den Vergleich ein.

[zB]Beispiel

Unterschied zwischen equals(…) und deepEquals(…):

int[][] a1 = { { 0, 1 }, { 1, 0 } };

int[][] a2 = { { 0, 1 }, { 1, 0 } };

System.out.println( Arrays.equals( a1, a2 ) ); // false

System.out.println( Arrays.deepEquals( a1, a2 ) ); // true

System.out.println( a1[0] ); // zum Beispiel [I@10b62c9

System.out.println( a2[0] ); // zum Beispiel [I@82ba41

Dass die Methoden unterschiedlich arbeiten, zeigen die beiden letzten Konsolenausgaben: Die von a1 und a2 unterreferenzierten Arrays enthalten die gleichen Elemente, sind aber zwei unterschiedliche Objekte, also nicht identisch.

[»]Hinweis

deepEquals(…) vergleicht auch eindimensionale Arrays:

Object[] b1 = { "1", "2", "3" };

Object[] b2 = { "1", "2", "3" };

System.out.println( Arrays.deepEquals( b1, b2 ) ); // true
class java.util.Arrays
  • static boolean equals(XXX[] a, XXX[] a2)

    Vergleicht zwei Arrays gleichen Typs und liefert true, wenn die Arrays gleich groß und Elemente paarweise gleich sind. XXX steht stellvertretend für boolean, byte, char, int, short, long, double, float.

  • static boolean equals(Object[] a, Object[] a2)

    Vergleicht zwei Arrays mit Objektverweisen. Ein Objekt-Array darf null enthalten; dann gilt für die Gleichheit e1==null ? e2==null : e1.equals(e2).

  • static boolean deepEquals(Object[] a1, Object[] a2)

    Liefert true, wenn die beiden Arrays ebenso wie alle Unter-Arrays – rekursiv im Fall von Unter-Objekt-Arrays – gleich sind.

Füllen von Arrays *

Arrays.fill(…) füllt ein Array mit einem festen Wert. Der Start-End-Bereich lässt sich optional angeben.

[zB]Beispiel

Fülle ein char-Array mit Sternchen:

char[] chars = new char[ 4 ];

Arrays.fill( chars, '*' );

System.out.println( Arrays.toString( chars ) ); // [*, *, *, *]
class java.util.Arrays
  • static void fill(XXX[] a, XXX val)

  • static void fill(XXX[] a, int fromIndex, int toIndex, XXX val)

    Setzt das Element val in das Array. Mögliche Typen für XXX sind boolean, char, byte, short, int, long, double, float oder mit Object beliebige Objekte. Beim Bereich ist fromIndex inklusiv und toIndex exklusiv.

Neben der Möglichkeit, ein Array mit festen Werten zu füllen, gibt es außerdem Methoden setAll(…)/parallelSetAll(…). Die Methoden durchlaufen ein gegebenes Array und rufen eine bestimmte Methode für jeden Index auf, die zur Initialisierung verwendet wird.

[zB]Beispiel *

Fülle ein double-Array mit Zufallszahlen:

double[] randoms = new double[10];

Arrays.setAll( randoms, v -> Math.random() );

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

Das Beispiel nutzt Lambda-Ausdrücke, um die Funktion zu beschreiben. In Kapitel 11, »Lambda-Ausdrücke und funktionale Programmierung«, kommen wir auf diese Syntax noch einmal zurück.

class java.util.Arrays
  • static void setAll(int[] array, IntUnaryOperator generator)

  • static void setAll(double[] array, IntToDoubleFunction generator)

  • static void setAll(long[] array, IntToLongFunction generator)

  • static <T> void setAll(T[] array, IntFunction<? extends T> generator)

  • static void parallelSetAll(double[] array, IntToDoubleFunction generator)

  • static void parallelSetAll(int[] array, IntUnaryOperator generator)

  • static void parallelSetAll(long[] array, IntToLongFunction generator)

  • static <T> void parallelSetAll(T[] array, IntFunction<? extends T> generator)

    Läuft ein gegebenes Array komplett ab und übergibt dabei dem generator Schritt für Schritt den Index. Der Generator bildet den Index auf einen Wert ab, der wiederum zur Array-Initialisierung genutzt wird.

Array-Abschnitte kopieren *

Die Klasse Arrays bietet eine Reihe von copyOf(…)- und copyOfRange(…)-Methoden, die gegenüber clone() den Vorteil haben, dass sie auch Bereichsangaben erlauben und das neue Array größer machen können; im letzten Fall füllen die Methoden das Array je nach Typ mit null, false oder 0.

[zB]Beispiel

String[] snow = { "Neuschnee", "Altschnee", "Harsch", "Firn" };

String[] snow1 = Arrays.copyOf( snow, 2 ); // [Neuschnee, Altschnee]

String[] snow2 = Arrays.copyOf( snow, 5 ); // [Neuschnee, Altschnee, Harsch,

// Firn, null]

String[] snow3 = Arrays.copyOfRange( snow, 2, 4 ); // [Harsch, Firn]

String[] snow4 = Arrays.copyOfRange( snow, 2, 5 ); // [Harsch, Firn, null]
class java.util.Arrays
  • static boolean[] copyOf(boolean[] original, int newLength)

  • static byte[] copyOf(byte[] original, int newLength)

  • static char[] copyOf(char[] original, int newLength)

  • static double[] copyOf(double[] original, int newLength)

  • static float[] copyOf(float[] original, int newLength)

  • static int[] copyOf(int[] original, int newLength)

  • static long[] copyOf(long[] original, int newLength)

  • static short[] copyOf(short[] original, int newLength)

  • static <T> T[] copyOf(T[] original, int newLength)

  • static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType)

  • static boolean[] copyOfRange(boolean[] original, int from, int to)

  • static byte[] copyOfRange(byte[] original, int from, int to)

  • static char[] copyOfRange(char[] original, int from, int to)

  • static double[] copyOfRange(double[] original, int from, int to)

  • static float[] copyOfRange(float[] original, int from, int to)

  • static int[] copyOfRange(int[] original, int from, int to)

  • static long[] copyOfRange(long[] original, int from, int to)

  • static short[] copyOfRange(short[] original, int from, int to)

  • static <T> T[] copyOfRange(T[] original, int from, int to)

  • static <T,U> T[] copyOfRange(U[] original, int from, int to,

    Class<? extends T[]> newType)

    Erzeugt ein neues Array mit der gewünschten Größe bzw. dem angegebenen Bereich aus einem existierenden Array. Wie üblich ist der Index from inklusiv und to exklusiv.

[zB]Beispiel *

Hänge zwei Arrays aneinander. Das ist ein gutes Beispiel für copyOf(…), wenn das Ziel-Array größer ist:

public static <T> T[] concat( T[] first, T[] second ) {

T[] result = Arrays.copyOf( first, first.length + second.length );

System.arraycopy( second, 0, result, first.length, second.length );



return result;

}

Hinweis: Das Beispiel nutzt Generics, um den Typ flexibel zu halten. Zum einfacheren Verständnis können wir uns T als Typ Object vorstellen und das, was in spitzen Klassen steht, löschen.

Halbierungssuche *

Ist das Array sortiert, lässt sich mit Arrays.binarySearch(…) eine binäre Suche (Halbierungssuche) durchführen. Ist das Array nicht sortiert, ist das Ergebnis unvorhersehbar. Findet binarySearch(…) das Element, ist der Rückgabewert der Index der Fundstelle, andernfalls ist die Rückgabe negativ.

[zB]Beispiel

Suche ein Element im sortierten Array:

int[] numbers = { 1, 10, 100, 1000 };

System.out.println( Arrays.binarySearch( numbers, 100 ) ); // 2

Ist das Array nicht aufsteigend sortiert, ist ein falsches Ergebnis die Folge.

binarySearch(…) liefert in dem Fall, dass das Element nicht im Array ist, eine kodierte Position zurück, an der das Element eingefügt werden könnte. Damit der Index nicht mit einer normalen Position einer Fundstelle kollidiert – die immer >= 0 ist –, ist die Rückgabe negativ und als -Einfügeposition - 1 kodiert.

[zB]Beispiel

Das Element 101 ist nicht im Array:

int[] numbers = { 1, 10, 100, 1000 };

System.out.println( Arrays.binarySearch( numbers, 101 ) ); // –4

Die Rückgabe ist –4, denn –4 = –3 – 1, was eine mögliche Einfügeposition von 3 ergibt. Das ist korrekt, denn 101 käme wie folgt ins Array: 1 (Position 0), 10 (Position 1), 100 (Position 2), 101 (Position 3).

[+]Tipp

Da das Array bei Arrays.binarySearch(…) zwingend sortiert sein muss, kann ein vorangehendes Arrays.sort(…) dies vorbereiten:

int[] numbers = { 10, 100, 1000, 1 };

Arrays.sort( numbers );

System.out.println( Arrays.toString( numbers ) ); // [1, 10, 100, 1000]

System.out.println( Arrays.binarySearch( numbers, 100 ) ); // 2

Die Sortierung ist nur einmal nötig und sollte nicht unnötigerweise wiederholt werden.

class java.util.Arrays
  • static int binarySearch(XXX[] a, XXX key)

    Sucht mit der Halbierungssuche nach einem Schlüssel. XXX steht stellvertretend für byte, char, int, long, float, double.

  • static int binarySearch(Object[] a, Object key)

    Sucht mit der Halbierungssuche nach key. Die Objekte müssen die Schnittstelle Comparable implementieren; das bedeutet im Allgemeinen, dass die Elemente vom gleichen Typ sein müssen – also nicht Strings und Hüpfburg-Objekte gemischt.

  • static <T> int binarySearch(T[] a, T key, Comparator<? super T> c)

    Sucht mit der Halbierungssuche ein Element im Objekt-Array. Die Vergleiche übernimmt ein spezielles Vergleichsobjekt c.

  • static <T> int binarySearch(T[] a, int fromIndex, int toIndex,

    T key,  Comparator<? super T> c)

    Schränkt die Binärsuche auf Bereiche ein.

Die API-Dokumentation von binarySearch(…) ist durch Verwendung der Generics (mehr darüber folgt in Kapitel 10, »Generics<T>«) etwas schwieriger. Wir werden in Kapitel 16, »Einführung in Datenstrukturen und Algorithmen«, auch noch einmal auf die statische Methode binarySearch(…) für beliebige Listen zurückkommen und insbesondere die Bedeutung der Schnittstellen Comparator und Comparable in Kapitel 9, »Besondere Typen der Java SE«, genau klären.

Arrays zu Listen mit Arrays.asList(…) – praktisch für die Suche und zum Vergleichen *

Ist das Array unsortiert, funktioniert binarySearch(…) nicht. Die Klasse Arrays hat für diesen Fall keine Methode im Angebot – eine eigene Schleife muss her. Es gibt aber noch eine Möglichkeit: Die statische Methode Arrays.asList(…) dekoriert das Array als Liste vom Typ java.util.List, die dann praktische Methoden wie contains(…), equals(…) oder subList(…) anbietet. Mit den Methoden sind Dinge auf Arrays möglich, für die Arrays bisher keine Methoden definierte.

[zB]Beispiel

Teste, ob auf der Kommandozeile der Schalter -? gesetzt ist. Die auf der Kommandozeile übergebenen Argumente übergibt die Laufzeitumgebung als String-Array an die main(String[] args)-Methode:

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

...

[zB]Beispiel

Teste, ob Teile zweier Arrays gleich sind:

// Index 0 1 2

String[] a = { "Asus", "Elitegroup", "MSI" };

String[] b = { "Elitegroup", "MSI", "Shuttle" };



System.out.println( Arrays.asList( a ).subList( 1, 3 ).

equals( Arrays.asList( b ).subList( 0, 2 ) ) ); // true

Im Fall von subList(…) ist der Start-Index inklusiv und der End-Index exklusiv (das ist die Standardnotation von Bereichen in Java, etwa auch bei substring(…) oder fill(…)). Somit werden im obigen Beispiel die Einträge 1 bis 2 aus a mit den Einträgen 0 bis 1 aus b verglichen.

class java.util.Arrays
  • static <T> List<T> asList(T... a)

    Liefert eine Liste vom Typ T bei einem Array vom Typ T.

Die statische Methode asList(…) nimmt über das Vararg entweder ein Array von Objekten (kein primitives Array!) an oder aufgezählte Elemente.

[»]Hinweis

Im Fall der aufgezählten Elemente ist auch kein oder genau ein Element erlaubt:

System.out.println( Arrays.asList() ); // []

System.out.println( Arrays.asList("Chris") ); // [Chris]

[»]Hinweis

Ein an Arrays.asList(…) übergebenes primitives Array liefert keine Liste von primitiven Elementen (es gibt keine List, die mit primitiven Werten gefüllt ist):

int[] nums = { 1, 2 };

System.out.println( Arrays.asList(nums).toString() ); // [[I@82ba41]

System.out.println( Arrays.toString(nums) ); // [1, 2]

Der Grund ist einfach: Arrays.asList(…) erkennt nums nicht als Array von Objekten, sondern als genau ein Element einer Aufzählung. So setzt die statische Methode das Array mit Primitiven als ein Element in die Liste, und die Objektmethode toString() eines java.util.List-Objekts ruft lediglich auf dem Array-Objekt toString() auf, was die kryptische Ausgabe zeigt.

 

Zum Seitenanfang

3.8.20Eine lange Schlange Zur vorigen ÜberschriftZur nächsten Überschrift

Das neu erworbene Wissen über Arrays wollen wir für unser Schlangenspiel nutzen, indem die Schlange länger wird. Bisher hatte die Schlange keine Länge, sondern nur eine Position auf dem Spielbrett; das wollen wir ändern, indem sich ein Programm im Array immer die letzten Positionen merken soll. Folgende Änderungen sind nötig:

  • Anstatt die Position in einem Point-Objekt zu speichern, liegen die letzten fünf Positionen in einem Point[] snakePositions.

  • Trifft der Spieler einen dieser fünf Schlangenpunkte, ist das Spiel verloren. Ist bei der Bildschirmausgabe eine Koordinate gleich einem der Schlangenpunkte, zeichnen wir ein »S«. Der Test, ob eine der Schlangenkoordinaten einen Punkt p trifft, wird mit Arrays.asList(snakePositions).contains(p) durchgeführt.

Ein weiterer Punkt ist, dass die Schlange sich bewegt, aber das Array mit den fünf Punkten immer nur die letzten fünf Bewegungen speichert – die alten Positionen werden verworfen. Das Programm realisiert das mit einem Ringspeicher – zusätzlich zu den Positionen verwalten wir einen Index, der auf den Kopf zeigt. Jede Bewegung der Schlange setzt den Index eine Position weiter, bis am Ende des Arrays der Index wieder bei 0 steht.

Eine symbolische Darstellung mit möglichen Punkten verdeutlicht dies:

snakeIdx = 0

snakePositions = { [2,2], null, null, null, null }

snakeIdx = 1

snakePositions = { [2,2], [2,3], null, null, null }

...

snakeIdx = 4

snakePositions = { [2,2], [2,3], [3,3], [3,4], [4,4] }

snakeIdx = 0

snakePositions = { [5,5], [2,3], [3,3], [3,4], [4,4] }

Alte Positionen werden überschrieben und durch neue ersetzt.

Das ganze Spiel mit den Änderungen, die fett hervorgehoben sind:

Listing 3.30LongerZZZZZnake.java

import java.awt.Point;

import java.util.Arrays;



public class LongerZZZZZnake {



public static void main( String[] args ) {

Point playerPosition = new Point( 10, 9 );

Point goldPosition = new Point( 6, 6 );

Point doorPosition = new Point( 0, 5 );

Point[] snakePositions = new Point[5];

int snakeIdx = 0;

snakePositions[ snakeIdx ] = new Point( 30, 2 );

boolean rich = false;



while ( true ) {

if ( rich && playerPosition.equals( doorPosition ) ) {

System.out.println( "Gewonnen!" );

break;

}

if ( Arrays.asList( snakePositions ).contains( playerPosition ) ) {

System.out.println( "ZZZZZZZ. Die Schlange hat dich!" );

break;

}

if ( playerPosition.equals( goldPosition ) ) {

rich = true;

goldPosition.setLocation( –1, –1 );

}



// Raster mit Figuren zeichnen



for ( int y = 0; y < 10; y++ ) {

for ( int x = 0; x < 40; x++ ) {

Point p = new Point( x, y );

if ( playerPosition.equals( p ) )

System.out.print( '&' );

else if ( Arrays.asList( snakePositions ).contains( p ) )

System.out.print( 'S' );

else if ( goldPosition.equals( p ) )

System.out.print( '$' );

else if ( doorPosition.equals( p ) )

System.out.print( '#' );

else

System.out.print( '.' );

}

System.out.println();

}



// Konsoleneingabe und Spielerposition verändern



switch ( new java.util.Scanner( System.in ).next() ) {

case "h" : playerPosition.y = Math.max( 0, playerPosition.y – 1 ); break;

case "t" : playerPosition.y = Math.min( 9, playerPosition.y + 1 ); break;

case "l" : playerPosition.x = Math.max( 0, playerPosition.x – 1 ); break;

case "r" : playerPosition.x = Math.min( 39, playerPosition.x + 1 ); break;

}



// Schlange bewegt sich in Richtung Spieler



Point snakeHead = new Point( snakePositions[snakeIdx].x,

snakePositions[snakeIdx].y );



if ( playerPosition.x < snakeHead.x )

snakeHead.x--;

else if ( playerPosition.x > snakeHead.x )

snakeHead.x++;

if ( playerPosition.y < snakeHead.y )

snakeHead.y--;

else if ( playerPosition.y > snakeHead.y )

snakeHead.y++;



snakeIdx = (snakeIdx + 1) % snakePositions.length;

snakePositions[snakeIdx] = snakeHead;

} // end while

}

}

Wer wieder etwas vorarbeiten möchte, kann Folgendes tun:

  • Ersetze das Array durch eine dynamische Datenstruktur vom Typ ArrayList<Point>.

  • Nach jedem zweiten Schritt vom Benutzer soll die Länge der Schlage um eins wachsen.

  • Wenn der Spieler ein Goldstück einsammelt, soll die Länge der Schlange um eins schrumpfen.

 


Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück
 Zum Katalog
Zum Katalog: Java ist auch eine Insel Java ist auch eine Insel

Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?

Ihre Meinung



 Buchempfehlungen
Zum Katalog: Java ist auch eine Insel

Java ist auch eine Insel




Zum Katalog: Java SE 9-Standard-Bibliothek

Java SE 9-Standard-Bibliothek




Zum Katalog: Professionell entwickeln mit Java EE 8

Professionell entwickeln mit Java EE 8




Zum Katalog: Entwurfsmuster

Entwurfsmuster




Zum Katalog: IT-Projektmanagement

IT-Projektmanagement




 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2017

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.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de