Inhalt |
|
|
|
Vereinfachte for-Schleife | |
|
|
|
|
|
|
|
|
|
|
|
Bisher können wir Variablen der in Java vorgegebenen Datentypen verwenden. Dabei kann jede Variable genau einen Wert des Datentyps speichern. Arrays geben nun die Möglichkeit, eine beliebige Anzahl von Variablen gleichen Typs simultan zu erzeugen und zu verwenden. Der Platz für die Daten wird auf dem Heap bereitgestellt. Auf dem Stack, der ja die lokalen Daten eines Unterprogramms aufnimmt, wird nur eine Referenz gespeichert.
Variablen, die Arrays (eigenlich Referenzen auf Arrays) aufnehmen können, werden mit eckigen Klammern [] gekennzeichnet. Das Array an sich muss mit new bereitgestellt werden. Im folgenden Beispiel wollen wir 1000 Variablen vom type double erzeugen, die in einem Array zusammengefasst werden.
double a[]; a = new double[1000];
oder kürzer
double a[] = new double[1000];
Eine alternative Syntax ist
double[] a = new double[1000];
Diese Syntax beruht darauf, dass der Datentyp des Arrays a mit double[] bezeichnet werden kann. Im Unterschied zu anderen Programmiersprachen, hat ein konkretes Array zwar eine fest definierte Größe, die aber im Datentyp nicht angegeben wird. Sie muss lediglich bei der Erzeugung konkretisiert werden. Die Größe des Arrays lässt sich nachträglich mit a.length bestimmen, aber nicht ändern.Außerdem gibt es dynamisch wachsende Arrays, die allerdings etwas ineffizienter sind. Wir werden diese Arrays erst später kennenlernen.
Mit obigem Befehl stehen also 1000 double-Variablen zur Verfügung. Der Zugriff auf eine einzelne dieser Variablen erfolgt durch Indizierung. Dabei wird der gewünschte Index in eckigen Klammern angegeben. Als Beispiel speichern wir die Quadratzahlen 1^2, 2^2, ..., 1000^2 in dem Array ab.
for (i=0; i<1000; i++) a[i]=i*i;
Vorsicht: Man beachte, dass die Nummerierung mit 0 beginnt! Das letzte Element hat also den Index 999 (Größe minus 1).
Die allgemeine Syntax einer indizierten Array-Variable ist also
arrayname[index]
wobei index ein Ausdruck vom Typ int sein muss. Eine solche indizierte Variable kann wie jede andere Variable des entsprechenden Typs (im Beipspiel double) verwendet werden.
Hier ein anderes Beispiel, bei dem eine Variable des Arrays sowohl rechts als auch links von = erscheint.
a[0] = 1.0; for (i=1; i<1000; i++) a[i] = 1.01*a[i-1];
Dieses Beispiel setzt jedes Element des Arrays auf das 1.01-fache des vorherigen, beginnend mit 1. Man muss also das erste Element setzen und dann die Restlichen in einer Schleife bearbeiten.
Insgesamt werden Arrays wie folgt gespeichert.
Referenz a auf dem Stack ---> a[0],...,a[n-1] auf dem Heap
Dabei ist n die Größe des Arrays.
Falls ein Index außerhalb der Grenzen 0 bis n-1 liegt, wird eine index-out-of-bounds exception erzeugt. Bei unseren einfachen Test-Programmen führt dies zum Programmabbruch mit einer Fehlermeldung.
Wird die Initialisierung des Arrays mit new weggelassen, so hat die Referenz den vordefinierten Inhalt null. Jeder Versuch, auf das Array zuzugreifen, erzeugt dann eine sogenannte NullPointerException.
Arrays werden automatisch vom Heap entfernt, wenn sie im Programm nicht mehr benötigt werden. Java kann feststellen, wann dies der Fall ist. Man nennt diesen Vorgang Garbage Collection.
Es gibt eine einfachere Möglichkeit auf jedes Element eines Arrays zuzugreifen. Eine Schleifenvariable wird dabei gar nicht mehr benötigt. Das sieht so aus.
int a[] = new int[10]; for (int i=0; i<10; i++) a[i]=i*i; for (int k : a) System.out.println(k);
Die erste Schleife, in der der Inhalt von a gesetzt wird, ist uns schon bekannt. Die zweite Schleife ist eine Kurzform, bei der die Variable k alle Elemente von a durchläuft. Wie bei der normalen for-Schleife kann man diese Variable auch gleich in der Schleife deklarieren.
Man beachte zwei wichtige Dinge.
Aufgrund dieser Einschränkungen eignet sich diese Schleifenform mehr für den Zugriff auf allgemeine Kollektorklassen, die wir später kennenlernen.
Wir erzeugen 10000 Zufallszahlen und bestimmen den Mittelwert, die Standardabweichung, und zählen, wieviele Zahlen in jedem Intervall 0 bis 0.1, 0.1 bis 0.2 etc. liegen.
Es wird zunächst Platz für 10000 Zahlen in einem Array geschaffen. Dann werden 10000 Zufallszahlen erzeugt und in dem Array abgespeichert. Beim Hantieren mit dem Array beachte man immer, dass der Index von 0 bis 9999 laufen muss.
Ein weiteres Array I der Größe 10 nimmt schließlich die Zähler auf. I[0] soll z.B. die Anzahl der Zufallszahlen zwischen 0 und 0.1 enthalten. Der Index des Zählerintervalls wird aus der Zufallszahl durch Multiplikation mit 10 und Abschneiden des Nachkomma-Anteils bestimmt.
public class Test { public static void main(String args[]) { int n = 10000; // Anzahl der Zufallszahlen double R[] = new double[n]; // erzeugt Platz fuer n Zahlen int i; // Zufallszahlen: for (i = 0; i < n; i++) R[i] = Math.random(); // Mittelwert: double sum = 0; for (i = 0; i < n; i++) sum += R[i]; // berechne Summe double mean = sum / n; System.out.println("Mittelwert : " + mean); // Stichprobenabweichung: sum = 0; for (i = 0; i < n; i++) sum += (mean - R[i]) * (mean - R[i]); // Berechne Varianz: System.out.println("Standard-Abweichung : " + Math.sqrt(sum / (n - 1))); // Verteilung: int m = 10; int I[] = new int[m]; // Platz fuer m Zaehler int j; for (j = 0; j < m; j++) I[j] = 0; // mit 0 initialisieren for (i = 0; i < n; i++) // zaehlen { // in welchem Intervall liegt R[i]? j = (int) Math.floor(R[i] * m); I[j]++; // erhoehe Zaehler } // Ausgabe: for (j = 0; j < m; j++) // Ausgabe der Zaehlungen System.out.println((j + 1) + " : " + I[j]); } }
Einige Dinge sind erklärungsbedürftig.
Man beachte, dass die Größe des Arrays im new-Befehl auch durch eine Variable oder einen Ausdruck angegeben werden kann. Das Programm erzeugt die folgende Ausgabe
Mittelwert : 0.5000445575184346 Standard-Abweichung : 0.28746642473628076 1 : 967 2 : 991 3 : 1060 4 : 992 5 : 1001 6 : 988 7 : 1065 8 : 952 9 : 993 10 : 991
Diese Ausgabe ist nicht jedesmal gleich. Im Unterschied zu C wird der Zufallsgenerator bei jedem Lauf mit der Systemzeit initialisiert.
Das folgende Programm erzeugt Zufallszahlen und sortiert sie. Der Sortieralgorithmus ist der denkbar schlechteste, aber auch der einfachste. Daher ist dieses Programm nur für kleine Datenmengen brauchbar.
Der Algorithmus funktioniert so: Die Zahlen R[2] bis R[n] werden solange nach vorne getauscht, bis sie an der richtigen Stelle stehen. Man nennt dies Bubble-Sort.
Durch 2. und 3. wird die j-te Zahl an die richtige Stelle sortiert.
public class Test { public static void main(String args[]) { int n = 20; double R[] = new double[n]; double h; int i, j; // Erzeuge Zufallszahlen: for (i = 0; i < n; i++) R[i] = Math.random(); // Sortiere: for (i = 1; i < n; i++) { j = i; // Sortiere R[i] ein while (j > 0) // solange es nicht ganz vorne steht { if (R[j - 1] > R[j]) // steht noch falsch { h = R[j]; R[j] = R[j - 1]; R[j - 1] = h; // vertauschen } else break; // steht richtig j--; } } // Ausgabe: for (i = 0; i < n; i++) System.out.println(R[i]); } }
Man kann ein Array schon bei der Deklaration mit Werten versehen. In diesem Fall ist kein new notwendig. Beispiel:
int n[] = {2,5,3,1};
System.out.println(n.length); // druckt 4
Die Größe des Arrays ergibt sich aus der Anzahl der angegebenen Werte. Diese Werte brauchen keine Konstanten zu sein.
In Java werden mehrdimensionale Arrays (Matrizen etc.) durch Arrays von Arrays erzeugt. Dies ist aber unter Umständen für den Programmierer transparent, d.h. er braucht sich darum nicht zu kümmern. Will man etwa Platz für 3 Reihen von 4 Elementen schaffen, so genügt es
int M[][] = new int[3][4];
zu setzen. Diese Matrix ist ein Array der Dimension 2. Schleifen über solche Matrizen müssen dann meist geschachtelte Schleifen sein. Etwa
int i,j; for (i=0; i<3; i++) for (j=0; j<4; j++) M[i][j] = i*j;
Der Zugriff erfolgt also durch ebensoviele Klammerpaare, wie die Dimension des Arrays angibt.
Dass es sich hier in der Tat um ein Array von drei Arrays handelt, zeigt folgendes Programm
int a[]=M[0]; for (i=0; i<a.length; i++) a[i]=i; System.out.println(a.length);
Hier wird die erste Zeile von M einem eindimensionalen Array mit vier Elementen zugewiesen. Also wird M folgendermaßen abgespeichert.
M ----> M[0], M[1],
M[2]
M[0] ----> M[0,0], M[0,1],
M[0,2], M[0,3]
M[1] ----> M[1,0], M[1,1],
M[1,2], M[1,3]
M[2] ----> M[2,0], M[2,1],
M[2,2], M[2,3]
Es ist in der Tat nicht notwendig so, dass mehrdimensionale Arrays rechteckig sein müssen. Jede Zeile kann eine andere Größe haben.
int M[][] = new int[3][]; for (int i=0; i<3; i++) M[i] = new int[i+1];
In diesem Beispiel haben die Zeilen von M die Längen 1,2,3. Es entsteht so ein dreieckiges Array.
Der wesentliche Unterschied zwischen den Befehlen
new int [3][4]; new int[3][];
ist also, dass im ersten Fall die Referenzen auf die Zeilen automatisch erzeugt werden und im zweiten Fall nicht. Der Programmierer muss in diesem Fall für die Initialisierung jeder einzelnen Zeile sorgen, bevor auf die Elemente zugegriffen werden kann.
Entsprechendes gilt für drei- und mehrdimensionale Arrays.
Wir erzeugen die ersten n Primzahlen. Wir wollen den verwendeten Algorithmus zunächst beschreiben
Die Schwierigkeit liegt darin diesen einfachen Algorithmus in ein Programm umzusetzen.
public class Test { public static void main(String args[]) { int n = 10; int p[] = new int[n]; // Setze die ersten beiden Primzahlen p[0] = 2; if (n > 1) p[1] = 3; // erzeuge den Rest: int i, j; for (i = 2; i < n; i++) // erzeuge p[2] bis p[n-1] { int m = p[i - 1] + 2; // versuche letzte Primzahl plus 2,4,... search: while (true) // eigentlich eine Dauerschleife { j = 0; while (true) // teste auf Teilbarkeit durch p[0],...,p[i-1] { if (m % p[j] == 0) break; // m ist nicht prim if (p[j] * p[j] > m || j >= i) // m ist prim, genuegend gesucht { p[i] = m; break search; } j++; } m += 2; } } // Ausgabe: for (i = 0; i < n; i++) System.out.println(p[i]); } }
Hier wird break mit einem Label benutzt. Dadurch wird die Suche nach der nächsten Primzahl abgebrochen, sobald eine Primzahl gefunden wurde. Dieses Beispiel wird deutlich lesbarer, wenn man Unterprogramme verwendet.
Der folgende Code löst ein Gleichungssystem mit dem Gaußschen Verfahren. Vermutlich ist dieses Beispiel wirklich nur für Mathematiker interessant. Wir beschreiben zunächst den verwendeten Algorithmus
public class Test { public static void main(String args[]) { double a[][] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 1 } }; double b[] = { 6, 15, 16 }; double h[]; int i, j, k, n = b.length; double m; int mk; // Mache M zur oberen Dreiecksmatrix: for (i = 0; i < n - 1; i++) { // Suche Maximum in einer Spalte unterhalb M[i][i]: m = Math.abs(a[i][i]); mk = i; // bisheriges Maximum und seine Zeile for (k = i + 1; k < n; k++) { if (Math.abs(a[k][i]) > m) { m = Math.abs(a[k][i]); mk = k; } } // Tausche eventuell Zeilen aus: if (mk != i) { h = a[i]; a[i] = a[mk]; a[mk] = h; m = b[i]; b[i] = b[mk]; b[mk] = m; } // Mache Spalte unterhalb M[i][i] zu 0: for (k = i + 1; k < n; k++) { double lambda = a[k][i] / a[i][i]; for (j = i + 1; j < n; j++) a[k][j] -= lambda * a[i][j]; b[k] -= lambda * b[i]; } } // Mache M zur Diagonalmatrix: for (i = n - 1; i >= 1; i--) { for (k = i - 1; k >= 0; k--) b[k] -= b[i] * a[k][i] / a[i][i]; } // Mache M zur Einheitsmatrix: for (i = 0; i < n; i++) b[i] /= a[i][i]; // Gib Ergebnis aus: for (i = 0; i < n; i++) System.out.println(b[i]); } }
Der Code dieses Beispiels ist schon recht kompliziert. Es ist unter Umständen schwer, einen Fehler (Bug) darin zu finden. Auch dieses Programm lässt sich durch Strukturieren mit Unterprogrammen lesbarer gestalten.