Java Standard: Arrays

(Weitergeleitet von Java Standard: Felder)


Häufig benötigt ein Programmierer mehrere zusammengehörige Variablen desselben Datentyps, die logisch oder verwaltungstechnisch zusammengehören. Es wäre aber sehr aufwendig, diese Variablen alle einzeln zu deklarieren und zu verarbeiten. Deswegen wird in Java, wie in anderen Programmiersprachen auch, die Verwendung von Arrays (deutsch etwa: Felder) unterstützt. In Arrays lassen sich alle primitiven Datentypen und alle Objekte speichern und systematisch bearbeiten. Alle Variablen haben einen gemeinsamen Namen, werden aber über unterschiedliche Indexe angesprochen.

Ein Array wird sehr ähnlich wie eine normale Variable deklariert: erst wird der Datentyp genannt, dann der Bezeichner für diese Variable. Bitte beachten Sie aber den Unterschied, dass hinter dem Datentyp eckige Klammern als Zeichen gesetzt werden. Sie zeigen an, dass wir es hier mit einem Array dieses Typs zu tun haben:

int [] array;

Im Gegensatz zu anderen Variablendeklarationen muss der Platz für das Array aber noch reserviert werden; die Deklaration sagt dem Compiler nur, unter welchem Namen das Feld angesprochen werden soll. (Für Leser mit Vorwissen aus anderen Programmiersprachen: Dies ist lediglich ein Zeiger. Solange dem Bezeichner kein Array zugewiesen wurde ist der Bezeichner mit null initialisiert.) Um den Platz für das Array zu reservieren und es damit funktionsfähig zu machen muss dieser Platz mit dem Schlüsselwort new ausdrücklich angefordert werden. Dafür gibt es mehrere Möglichkeiten. Die Einfachste nennt die Anzahl der Elemente, die in dem Feld gespeichert werden soll:

array = new int [10];

Jetzt verweist der Bezeichner array auf ein Feld von zehn Variablen des Typs int. Diese einzelnen Variablen sprechen wir an, indem wir den Namen des Feldes und den Index der gewünschten Variablen angeben. Im folgenden Beispiel setzen wir den Wert des 5. Wertes im Feld auf 3:

array [4] = 3;

Bitte beachten Sie dabei, dass die Zählung bei 0 beginnt. Wenn Sie den 5. Wert setzen wollen muss der Index also 4, der gewünschte Platz minus 1, sein.

Sie können einem Array auch bei der Definition Werte zuweisen:

int [] array = new int [] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

Auch dieses Array ist zehn Felder lang, die aber bei der Deklaration sofort initialisiert werden. Übrigens werden durch den Java-Compiler alle numerischen Variablen eines Arrays bei der Initialisierung automatisch auf 0 gesetzt; bei Objekten wird initial der Wert null zugewiesen. Eine manuelle Initialisierung auf diese Werte ist also überflüssig.

Bitte beachten Sie, dass Arrays bei der Definition eine konkrete Anzahl von Elementen zugewiesen wird, in unseren Beispielen zehn Werte. Diese Anzahl bleibt unveränderlich; es können weder zusätzliche Werte aufgenommen noch überflüssige Werte freigegeben werden. Java überprüft bei jedem Zugriff auf ein Array, ob der gewünschte Zugriff gültig ist. Falls der Index kleiner 0 oder größer als die Länge des Feldes ist wird eine Fehlermeldung generiert, die den Programmablauf normalerweise abbricht. Es ist also sinnvoll, den Zugriff auf Elemente des Arrays nur mit eigenen Methoden zu erlauben, die einen solchen Zugriff überwachen. Das könnte im einfachsten Fall folgendermaßen aussehen:

 public class JavaArrayTest1 {
	// Array definieren
	private double [] value;
  
	// Konstruktor; hier wird der Platz für das Array reserviert
	public JavaArrayTest1() {
		value = new double [10];   
	}
   
	// Methode zum Setzen eines Array-Wertes. Der Index wird überprüft.
	// Wenn der Index außerhalb des Gültigkeitsbereichs liegt wird der Aufruf ignoriert.
	public void setValue(int index, double wert) {
		if(index >= 0 && index < value.length)
			value [index] = wert;
	}
   
	// Methode zur Abfrage eines Array-Wertes. Der Index wird überprüft.
	// Wenn der Index außerhalb des Gültigkeitsbereichs liegt wird der Wert 0.0 zurück geliefert.
	public double getValue(int index) {
		double result = 0.0;

		if(index >= 0 && index < value.length)
			result = value [index];

		return result;
	}  
 }

Diese - sehr einfache - Fehlerbehandlung sorgt dafür, dass das Programm nur gültige Indexe verarbeitet. Wenn das Array also wie hier mit dem Schlüsselwort private für andere Klassen verborgen wird, dann kann der Zugriff nur mit den von außen sichtbaren Methoden setValue() und getValue() erfolgen, die die Gültigkeit des Zugriffs sicherstellen. Das Programm läuft stabiler.

Zusätzlich wird in den gezeigten Methoden auf eine Eigenschaft zugegriffen, die wir noch ansprechen müssen: Die Länge des Arrays. Sie wird für jedes Array in der Variablen length gespeichert, die das Programm jederzeit abfragen kann, indem der Bezeichner des Arrays durch einen Punkt und den Variablennamen length ergänzt wird. Hier geschieht es in den Abfragen zum Gültigkeitsbereich: Der an die Methode übergebene Index muss kleiner sein als der Wert in value.length. Dadurch können weitere Fehler beim Programmieren ausgeschlossen werden. Falls nämlich das Feld value im Zuge weiterer Programmierungen in der Größe geändert werden muss, kann der Programmierer einfach die Deklaration im Kopf der Klasse ändern - und fertig. Alle Abfragen passen sich automatisch an, denn der Compiler ändert während der Übersetzung des Programmcodes diese Variable in den aktuellen Wert. Außerdem wird der Quellcode der Klasse leichter lesbar, denn durch die Verwendung des Namens statt einer Zahl wird deutlicher, was diese konkrete Programmierung bewirken soll.

Standardlösungen: Die Klasse java.util.Arrays

Bearbeiten

Für viele Bearbeitungen, die auf Arrays angewendet werden können, gibt es in Java bereits Lösungen. Einige von ihnen sind in der Klasse Arrays verwirklicht, die im Package java.util untergebracht ist. Zu diesen Standardaufgaben gehören unter Anderem die Initialisierung, die Sortierung und die Suche von Werten in Arrays. Um diese Methoden zu nutzen genügt der einfache Aufruf mit dem vorgesetzten Klassenpfad, denn die Methoden sind alle statisch. Zwei Beispiele für solche Aufrufe, "fill()" und "sort()", finden Sie in folgendem Code, den Sie in Ihre Programmier-Umgebung kopieren und dort ausführen lassen können; er ist in dieser Form vollständig lauffähig.

package javaarraytest;

public class JavaArrayTest {
	public static void main(String[] args) {
		// Array definieren
		double [] array;

		// Initialisierung des Arrays; gehört normalerweise in den Konstruktor
		array = new double[10];

		// Mit Zahlen initialisieren
		java.util.Arrays.fill(array, -1.0);

		// Ausgabe des Ergebnisses auf dem Bildschirm
		print(array, "Initialisierung mit java.util.Arrays.fill()");

		// Initialisierung eines Teilbereiches
		java.util.Arrays.fill(array, 3, 6, -2.0);

		// Erneute Testausgabe
		print(array, "Initialisierung eines Teilbereiches");

		// Nun mit Zufallswerten zwischen 0.0 und 1.0 füllen
		for(int i=0; i<array.length; i++)
			array[i] = Math.random();
	
		// Wieder ausgeben
 		print(array, "Gefüllt mit Zufallszahlen");

		// Sortieren
		java.util.Arrays.sort(array);

		// Und wieder ausgeben
		print(array, "Sortiert mit java.util.Arrays.sort()");

		// Für eigene Tests: Hier einfügen; Ausgabe mit "print()", wie oben

		// Ende der main()-Methode
		return;
  	}
    
	// Ausgabeschleife; als eigene Funktion, da sie mehrfach benötigt wird
	private static void print(double[] array, String title) {
		System.out.println(title); // Überschrift anzeigen

		// Jede einzelne Zahl in eigener Zeile und nummeriert anzeigen
		for(int i=0; i<array.length; i++)
			System.out.println(i+1 + ". Zahl: " + array[i]);

		// Eine Leerzeile als Abstand zur nächsten Ausgabe einfügen
		System.out.println();
	}

	// Variante von print(), um Anzeigefehler bei eigenen Tests zu vermeiden
	private static void print(double[] array) {
		print(array, "");
	}
}

In diesem Beispiel werden zwei verschiedene Methoden der Klasse Arrays aufgerufen. Die Erste ist fill(), mit der das Array mit einem vorgegebenen Wert, hier -1, initialisiert wird. In einem zweiten Aufruf wird nur ein Teil des Arrays befüllt, indem zusätzliche Parameter für den gewünschten Bereich angegeben werden. Die Parameter geben die Untergrenze (hier 3) und die Obergrenze (hier: 6) des Index an, die für den Aufruf gelten. Dabei wird die Untergrenze eingeschlossen, die Obergrenze aber ausgeschlossen. Der Aufruf füllt in diesem Fall also die Felder array[3], array[4] und array[5] mit dem Wert -2, belässt aber den Wert array[6] wie er ist. Dieses Verhalten gilt für alle Methoden der Klasse Arrays, die Teilbearbeitungen ermöglichen.

Wichtig ist ebenfalls, dass die geänderten Felder den Platz 4, 5 und 6 im Array besetzen, denn die Java-interne Zählung der Felder beginnt bei 0 statt bei 1. In der Funktion print() ist das an der Ausgabeschleife gut zu verfolgen, denn die Schleife beginnt bei 0 und läuft nur bis zur Länge des Feldes -1, in unserem Beispiel also bis 9.

Die Übersicht über die Klasse Arrays und ihrer Funktionen finden Sie hier. Es lohnt sich, die dort genannten Lösungen für Standardaufgaben zu kennen. Sie beschleunigen die eigene Arbeit und sind zudem bereits auf korrekte Funktion geprüft.

Mehrdimensionale Arrays

Bearbeiten

In vielen Programmiersprachen sind auch Arrays von Arrays möglich; so auch in Java. Die Deklaration dieser Arrays erfolgt durch entsprechend häufiges Setzen der eckigen Klammern:

double [][] array2;
double [][][] array3;

Mit diesen Aufrufen wird für array2 ein zweidimensionales Array aus double-Werten definiert, für array3 sogar ein dreidimensionales Array (also ein Array aus Arrays aus Arrays). Diese Vervielfachung lässt sich beliebig tief stapeln.

Bei der Speicherreservierung gehen wir wieder vor wie bereits oben beschrieben und reservieren den Speicherplatz mit new. Im Folgenden gehen wir nur auf das zweidimensionale Beispiel ein; alle weiteren Ebenen werden in der gleichen Form behandelt.

Wie bei einem eindimensionalen Array muss auch ein mehrdimensionales Array seinen Speicherplatz mit dem Schlüsselwort new zugewiesen bekommen. Das kann geschehen, indem in beiden Klammern die gewünschte Zahl an Elementen angegeben wird:

array2 = new double [5][4];

Diese Form des Aufrufs ergibt eine Tabelle mit fünf Zeilen und vier Spalten. Es kann aber sinnvoll sein, die Anzahl der Spalten fortzulassen und erst später eine entsprechende Zuweisung vorzunehmen. Das sieht dann wie folgt aus:

array2 = new double [5][];

Diese Definition reserviert den Platz für fünf später zuzuweisende Arrays von double-Werten, deren Größe aber noch nicht genannt wird. Einem solchen Array lässt sich ein anderes Array folgendermaßen zuweisen:

double [][] array2;
double [] test = {1.0, 2.0, 3.0};

array2 = new double [5][];
array2 [4] = test;

Damit hat das Array array2 den Zeiger auf das Array test gespeichert. Es entsteht keine Kopie des Arrays test; vielmehr können wir auf die Werte des Arrays nun unter zwei verschiedenen Namen zugreifen. Wenn wir also den Wert test [2] von 3.0 auf 4.0 ändern und danach den Wert array2 [4][2] abfragen bekommen wir als Ergebnis 4.0 zurück.

Dieses Vorgehen ermöglicht auch die Speicherung verschieden langer Arrays in einem multidimensionalen Array. Wenn wir ein Array test2 definieren, das aus lediglich zwei Werten besteht, dann können wir es trotzdem im übergeordneten Array array2 speichern.

Werden Arrays auf diese Weise zugewiesen muss der Programmierer aber vor Abfragen immer sicherstellen, dass das untergeordnete Array, auf das zugegriffen werden soll, tatsächlich existiert, weil sonst ein Zugriff auf ein Feld versucht wird, das mit null initiiert ist. Das löst eine Fehlermeldung aus, die zum Programmabbruch führt. Zusätzlich sollte der Programmierer Zugriffe vermeiden, die auf Werte hinter dem Ende des Unter-Arrays liegen, weil auch das zu einem Programmabbruch führt. Die Länge eines Unter-Arrays kann wieder über die Variable length abgefragt werden:

// Diese Abfrage ergibt die Anzahl von Plätzen für Unter-Arrays
int len1 = array2.length;

// Diese Abfrage ergibt die Anzahl von Werten im Unter-Array mit dem Index 4
int len2 = array2 [4].length;

Anonyme Arrays

Bearbeiten

In seltenen Fällen werden Arrays nur für die Initialisierung irgendwelcher Daten oder für den Aufruf von Methoden benötigt. In solchen Fällen kann man sich die Vergabe eines Bezeichners sparen. Das sollten Sie aber immer wohlüberlegt machen, weil dadurch Übersichtlichkeit im Quellcode verloren geht.

Flexible Arrays

Bearbeiten

Eine wichtige Beschränkung bei der Arbeit mit Arrays ist die festgelegte Länge. In der Praxis werden aber häufig Listen und Felder benötigt, deren Größe zunächst unbekannt ist oder sich im Laufe der Arbeit ändern kann. Arrays sind in solchen Fällen keine gute Lösung. Java bietet dafür aber eine Reihe maßgeschneiderter Lösungen an, die im Java Collection Framework zusammengefasst sind. Die Arbeit mit diesen Klassen ist aber etwas komplizierter als die Arbeit mit Arrays. Wir werden auf sie im Abschnitt über fortgeschrittene Programmierung zurückkommen. An dieser Stelle sei nur darauf hingewiesen, dass es diese Problemlösungen bereits gibt; Sie müssen keine zusätzliche Zeit für die Lösung dieser Aufgaben einplanen.

Exceptions bei der Arbeit mit Arrays

Bearbeiten

Die Arbeit mit Exceptions wird zwar erst später in diesem Buch besprochen, doch damit das Thema Arrays geschlossen behandelt werden kann ergänzen wir hier noch die üblichen Fehlermeldungen, die beim Arbeiten mit Arrays ausgelöst werden. Sofern Sie eigene Methoden definieren, die auf Fehler bei der Eingabe so reagieren sollen wie die Klassen der Java-Bibliotheken - was empfehlenswert ist - und sie den auftretenden Fehler nicht in der aufgerufenen Methode abfangen und behandeln können oder wollen, sollten Sie diese Exceptions benutzen.

Es handelt sich um folgende Exceptions:

  • ArrayIndexOutOfBoundsException - Wird ausgelöst, falls bei einem Index ein Wert kleiner 0 oder größer als die vorgegebene Länge des Feldes angegeben wurde.
  • ArrayStoreException - Es wurde versucht, dem Array einen falschen Typ hinzu zu fügen, zum Beispiel ein Integer-Wert einem String-Array.
  • NegativeArraySizeException - Wird ausgelöst, wenn das Programm versucht, ein Array mit weniger als 0 Werten zu erzeugen.

Zusätzlich sind folgende allgemeine Exceptions üblich:

  • NullPointerException - Wird an die aufgerufene Methode als Parameter statt der erwarteten Referenz auf ein Array nur null übergeben wird diese Exception ausgelöst.
  • IllegalArgumentException - Falls bei einem Methoden-Aufruf mit zwei Parametern, die einen Teilbereich des Arrays definieren, der Index, der die Untergrenze darstellt, größer ist als der Index der Obergrenze.