1.08 Arrays

Motivation

Schauen wir uns hier als Einleitung einfach direkt ein Beispielprogramm an. Dieses enthält noch nichts neues, aber Du wirst sicherlich schon sehen, was wir hier gerne verschönern wollen:

import java.util.Scanner;

public class Mittelwert {

  public static void main(String[] args) {

        Scanner meinScanner = new Scanner(System.in);
        
        double ersteZahl;
        double zweiteZahl;
        double dritteZahl;
        	    
        System.out.print("Eingabe 1. Zahl: ");
        ersteZahl = meinScanner.nextDouble();
        System.out.print("Eingabe 2. Zahl: ");
        zweiteZahl = meinScanner.nextDouble();
        System.out.print("Eingabe 3. Zahl: ");
        dritteZahl = meinScanner.nextDouble();
        
        meinScanner.close();
        
        System.out.println("Eingegeben wurden:");
        System.out.println(ersteZahl);
        System.out.println(zweiteZahl);
        System.out.println(dritteZahl);
        
        System.out.println("Summe:");
        System.out.println(ersteZahl + zweiteZahl + dritteZahl);
        
        System.out.println("Mittelwert:");
        System.out.println((ersteZahl + zweiteZahl + dritteZahl)/3);
        
  }

}

In diesem Programm werden zunächst drei double-Werte eingelesen. Danach werden sie noch einmal angezeigt. Außerdem wird auch ihre Summe ausgegeben und schließlich noch ihr arithmetisches Mittel („Durchschnitt“).

Beim Testen des Beispiels müssen wir beachten, dass wir in der Konsole die Dezimalzahlen mit einem Komma statt eines Punktes angeben müssen. Hier ein möglicher Ablauf:

Eingabe 1. Zahl: 1,5
Eingabe 2. Zahl: 2,5
Eingabe 3. Zahl: 4
Eingegeben wurden:
1.5
2.5
4.0
Summe:
8.0
Mittelwert:
2.6666666666666665

Was gibt es an diesem Beispiel auszusetzen?

Schon das Einlesen der drei Zahlen könnten wir doch eleganter lösen, oder? Es wäre doch viel schöner, wenn wir hier eine Schleife einsetzen könnten. Leider gibt es hier aber das Problem, dass wir in einer Schleife nicht die drei Variablen ersteZahl, zweiteZahl und dritteZahl durchgehen können. Auch die Ausgabe der drei Zahlen scheitert daran.

Außerdem ist unser Programm sehr unflexibel. Man muss immer genau drei Zahlen eingeben. Wäre es nicht schöner, ein Programm zu haben, bei dem man sich aussuchen kann, von wie vielen Zahlen der Durchschnitt berechnet werden soll?

Verwendung eines Arrays

Eindimensionale Arrays

Die Lösung besteht in der Verwendung eines Arrays. Wenn wir uns eine „normale“ Variable wie einen Kasten vorstellen (siehe Lektion zu Variablen, dann können wir uns ein Array so vorstellen:

Ein Array kann man sich wie einen großen Kasten mit mehreren Fächern vorstellen.
Ein Array kann man sich wie einen großen Kasten mit mehreren Fächern vorstellen. Anklicken zum Vergrößern.

In diesem Bild wird ein Array für int-Werte mit dem Namen meineZahlen dargestellt. Die einzelnen Fächer eines solchen Array sich durchnummeriert. Vorsicht Falle: Die Nummerierung beginnt bei Null!

Wir ändern unser Programm von oben nun schrittweise so ab, dass wir geschickt ein Array einsetzen. Zunächst fliegen die drei double-Variablen raus und werden ein ein Array ersetzt:

import java.util.Scanner;

public class Mittelwert {

  public static void main(String[] args) {

        Scanner meinScanner = new Scanner(System.in);
        
        double [] meineZahlen;
        meineZahlen = new double [3];
        
        System.out.print("Eingabe 1. Zahl: ");
        meineZahlen[0] = meinScanner.nextDouble();
        
        System.out.print("Eingabe 2. Zahl: ");
        meineZahlen[1] = meinScanner.nextDouble();
        
        System.out.print("Eingabe 3. Zahl: ");
        meineZahlen[2] = meinScanner.nextDouble();
        
        meinScanner.close();
        
        System.out.println("Eingegeben wurden:");
        System.out.println(meineZahlen[0]);
        System.out.println(meineZahlen[1]);
        System.out.println(meineZahlen[2]);
        
        System.out.println("Summe:");
        System.out.println(meineZahlen[0] + meineZahlen[1] + meineZahlen[2]);
        
        System.out.println("Mittelwert:");
        System.out.println((meineZahlen[0] + meineZahlen[1] + meineZahlen[2])/3);
        
  }

}

Hier sehen wir, wie man ein Array anlegt. Zunächst wird es mit double[] meineZahlen; als Variable deklariert. Dass dies keine gewöhnliche Variable vom Typ double ist, sondern ein Array, sieht man an den eckigen Klammern. Danach wird es mit meineZahlen = new double [3]; angelegt. Erst an dieser Stelle wird seine Länge angegeben. Es ist momentan aber noch leer.

Beim Einlesen der Zahlen sehen wir, wie man auf die einzelnen Fächer des Arrays zugreifen kann. Die Fächer sind hier mit 0, 1 und 2 durchnummeriert. Wollen wir auf ein Fach zugreifen, müssen wir lediglich den Namen des Arrays angeben und dahinter in eckigen Klammern die Nummer des Fachs schreiben.

Bei der Ausgabe der drei Zahlen und der Berechnung von Summe und Mittelwert wird erneut auf die einzelnen Fächer zugegriffen.

Das Programm ist nun schon ein wenig eleganter, aber natürlich drängt sich hier der Einsatz einer Schleife an – vor allem beim Einlesen und Ausgeben der Zahlen. Da wir genau wissen, wie oft die Schleife wiederholt werden soll, ist eine Zählschleife hier sinnvoll:

import java.util.Scanner;

public class Mittelwert {

  public static void main(String[] args) {

    Scanner meinScanner = new Scanner(System.in);

    double [] meineZahlen;
    meineZahlen = new double [3];

    for (int zaehler=0; zaehler<3; zaehler=zaehler+1){
      System.out.print("Eingabe ");
      System.out.print(zaehler+1);
      System.out.print(". Zahl: ");
      meineZahlen[zaehler] = meinScanner.nextDouble();
    }

    meinScanner.close();

    System.out.println("Eingegeben wurden:");

    for (int zaehler=0; zaehler<3; zaehler=zaehler+1){
      System.out.println(meineZahlen[zaehler]);
    }

    System.out.println("Summe:");
    System.out.println(meineZahlen[0] + meineZahlen[1] + meineZahlen[2]);

    System.out.println("Mittelwert:");
    System.out.println((meineZahlen[0] + meineZahlen[1] + meineZahlen[2])/3);

  }

}

Der Zähler der Schleifen läuft jeweils von von 0 bis 2. Beim Einlesen müssen wir an einer Stelle System.out.print(zaehler+1); schreiben, damit nicht nach der 0., 1. und 2. Zahl sondern nach der 1., 2. und 3. Zahl gefragt wird.

Das Programm ist nun wieder etwas geschickter umgesetzt. Nun wollen wir es aber noch so verbessern, dass wir nicht auf drei Zahlen festgelegt sind, sondern es dem Benutzer freistellen, wie viele Zahlen er eingeben möchte.

Die Länge eines Arrays

Das Programm soll nun so ablaufen, dass es zu Beginn den Benutzer nach der gewünschten Länge fragt. Diese muss dann bei den Zählschleifen und den Berechnungen von Summe und Mittelwert berücksichtigt werden. Wir werden diese Länge allerdings nicht in einer Variablen speichern, sondern sofort zum Erstellen des Arrays verwenden:

import java.util.Scanner;

public class Mittelwert {

  public static void main(String[] args) {

    Scanner meinScanner = new Scanner(System.in);

    // Anlegen des Arrays gewünschter Größe
    double [] meineZahlen;
    System.out.print("Wie viele Zahlen sollen eingegeben werden? ");
    meineZahlen = new double [meinScanner.nextInt()];
    
    // Einlesen der Zahlen
    for (int zaehler=0; zaehler<meineZahlen.length; zaehler=zaehler+1){
      System.out.print("Eingabe ");
      System.out.print(zaehler+1);
      System.out.print(". Zahl: ");
      meineZahlen[zaehler] = meinScanner.nextDouble();
    }

    meinScanner.close();
    
    // Ausgabe der Zahlen
    System.out.println("Eingegeben wurden:");

    for (int zaehler=0; zaehler<meineZahlen.length; zaehler=zaehler+1){
      System.out.println(meineZahlen[zaehler]);
    }
    
    // Berechnung und Ausgabe der Summe
    System.out.println("Summe:");
    double summe;
    summe = 0;
    
    for (int zaehler=0; zaehler<meineZahlen.length; zaehler=zaehler+1){
      summe = summe + meineZahlen[zaehler];
    }
    
    System.out.println(summe);

    // Berechnung und Ausgabe des Mittelwerts
    System.out.println("Mittelwert:");
    System.out.println(summe/meineZahlen.length);

  }

}

Der Trick ist, dass wir die Länge des Arrays jederzeit mit meineZahlen.length noch einmal abfragen können. Daher ist es nicht nötig, eine zusätzliche Variable anzulegen, um dort die Länge zu speichern.

Die Länge eines Arrays lässt sich übrigens nicht ändern. Es lassen sich also weder zusätzliche Fächer hinzufügen, noch kann man einzelne Fächer entfernen. Das führt dazu, dass wir uns später noch Alternativen zu Arrays ansehen werden.

Hier kannst Du Dir die bisherigen Erklärungen noch einmal in einem Clip ansehen:


Wiedergabe stellt eine Verbindung zu YouTube her.

Mehrdimensionale Arrays

Wir können nicht nur Arrays von primitiven Datentypen anlegen, sondern auch Arrays von Arrays! Wir können uns also vorstellen, dass wir mehrere der Kästen mit den Fächern (siehe Abbildung oben) vor uns liegen haben. Diese werden dann wiederum in einem Array organisieren. Dies können wir uns ganz anschaulich wie eine Kommode mit mehreren Schubfächern vorstellen:

Eine Kommode mit mehreren Schubladen, nummeriert von 0 bis 4.
Jede Schublade ist wieder ein Array wie in der Abbildung weiter oben oben.

Sehen wir uns ein Beispiel in Java an:

public class ArrayZweidimensional {

  public static void main(String[] args) {
    
    int [][] meineZahlen;
    meineZahlen = new int[4][5];
    
    meineZahlen[0][0] = 0;
    meineZahlen[0][1] = 0;
    meineZahlen[0][2] = 0;
    meineZahlen[0][3] = 0;
    meineZahlen[0][4] = 0;
    
    meineZahlen[1][0] = 0;
    meineZahlen[1][1] = 1;
    meineZahlen[1][2] = 2;
    meineZahlen[1][3] = 3;
    meineZahlen[1][4] = 4;

    meineZahlen[2][0] = 0;
    meineZahlen[2][1] = 2;
    meineZahlen[2][2] = 4;
    meineZahlen[2][3] = 6;
    meineZahlen[2][4] = 8;
    
    meineZahlen[3][0] = 0;
    meineZahlen[3][1] = 3;
    meineZahlen[3][2] = 6;
    meineZahlen[3][3] = 9;
    meineZahlen[3][4] = 12;
    
    System.out.println(meineZahlen[2][3]);
    
  }

}

Wir sehen, dass ein solches Array von Arrays mit doppelten eckigen Klammern gekennzeichnet wird. Beim Erstellen des Arrays geben wir dann – anschaulich gesprochen – in der ersten Klammer die Anzahl der Schubladen und in der zweiten die Anzahl der Fächer pro Schublade an.

Die einzelnen Fächer sind nun durch die Schubladen- und Fachnummer gekennzeichnet, so dass dort Daten abgelegt und wieder ausgelesen werden können.

Ein solches Array nennt man zweidimensionales Array. In der Praxis stellt man es sich auch gerne als eine Tabelle vor, deren Einträge nach dem folgenden Schema nummeriert sind:

[0][0][0][1][0][2][0][3][0][4]
[1][0][1][1][1][2][1][3][1][4]
[2][0][2][1][2][2][2][3][2][4]
[3][0][3][1][3][2][3][3][3][4]

Die erste Zahl gibt die Zeile und die zweite die Spalte an.

So wie Zählschleifen sehr praktisch beim Einsatz von eindimensionales Array sind, sind verschachtelte Zählschleifen bei der Arbeit mit mehrdimensionalen Arrays sehr hilfreich. Dies sehen wir, wenn wir im Programm von oben eine Ausgabe der Zahlen ergänzen:

public class ArrayZweidimensional {

  public static void main(String[] args) {
    
    int [][] meineZahlen;
    meineZahlen = new int[4][5];
    
    meineZahlen[0][0] = 0;
    meineZahlen[0][1] = 0;
    meineZahlen[0][2] = 0;
    meineZahlen[0][3] = 0;
    meineZahlen[0][4] = 0;
    
    meineZahlen[1][0] = 0;
    meineZahlen[1][1] = 1;
    meineZahlen[1][2] = 2;
    meineZahlen[1][3] = 3;
    meineZahlen[1][4] = 4;

    meineZahlen[2][0] = 0;
    meineZahlen[2][1] = 2;
    meineZahlen[2][2] = 4;
    meineZahlen[2][3] = 6;
    meineZahlen[2][4] = 8;
    
    meineZahlen[3][0] = 0;
    meineZahlen[3][1] = 3;
    meineZahlen[3][2] = 6;
    meineZahlen[3][3] = 9;
    meineZahlen[3][4] = 12;
    
    for (int zeile=0; zeile<meineZahlen.length; zeile=zeile+1){
      for (int spalte=0; spalte<meineZahlen[0].length; spalte=spalte+1){
        System.out.print(meineZahlen[zeile][spalte]);
        System.out.print(" "); 	// Abstand zwischen den Spalten
      }
      System.out.println(); 		// Zeilenumbruch
    }
    
  }

}

Die Anzahl der Zeilen können wir, wie wir sehen, mit meineZahlen.length erfragen. Die Anzahl der Spalten ist in jeder Zeile gleich. Um diese Anzahl zu erfahren, nehmen wir einfach die oberste Schublade meineZahlen[0] und schauen uns die Anzahl der Fächer meineZahlen[0].length in dieser Schublade an.

Theoretisch können wir auch Arrays mit noch mehr Dimensionen verwenden. Ein dreidimensionales Array könnten wir uns wie mehrere Kommoden vorstellen, die hintereinander stehen. Oder auch als mehrere Tabellenblätter, die durchnummeriert sind. Hier ein kleines Beispiel in Java:

public class ArrayDreidimensional {

  public static void main(String[] args) {

    int [][][] meineZahlen;
    meineZahlen = new int[4][3][6];

    System.out.println(meineZahlen.length);			// Anzahl Tabellenseiten
    System.out.println(meineZahlen[0].length);		// Anzahl Zeilen
    System.out.println(meineZahlen[0][0].length);	// Anzahl Spalten
    
    // Füllen mit ein paar Zahlen

    for (int tabellennummer=0; tabellennummer<meineZahlen.length; tabellennummer=tabellennummer+1){

      for (int zeile=0; zeile<meineZahlen[0].length; zeile=zeile+1){

        for (int spalte=0; spalte<meineZahlen[0][0].length; spalte=spalte+1){

          meineZahlen[tabellennummer][zeile][spalte] = tabellennummer+zeile*spalte;
          
        }

      }

    }
    

    // Ausgabe

    for (int tabellennummer=0; tabellennummer<meineZahlen.length; tabellennummer=tabellennummer+1){

      for (int zeile=0; zeile<meineZahlen[0].length; zeile=zeile+1){

        for (int spalte=0; spalte<meineZahlen[0][0].length; spalte=spalte+1){

          System.out.print(meineZahlen[tabellennummer][zeile][spalte]);
          System.out.print(" "); 	// Abstand zwischen den Spalten
        }
        System.out.println(); 		// Zeilenumbruch

      }

      System.out.println(); 		
      System.out.println("Neue Seite");
      System.out.println();

    }

  }

}

Als Ausgabe erhalten wir dies:

4
3
6
0 0 0 0 0 0 
0 1 2 3 4 5 
0 2 4 6 8 10 

Neue Seite

1 1 1 1 1 1 
1 2 3 4 5 6 
1 3 5 7 9 11 

Neue Seite

2 2 2 2 2 2 
2 3 4 5 6 7 
2 4 6 8 10 12 

Neue Seite

3 3 3 3 3 3 
3 4 5 6 7 8 
3 5 7 9 11 13 

Neue Seite

Die Vorstellung, dass man Tabellen auf mehreren Seiten hat, ist natürlich nur eine Veranschaulichung. Bei Arrays mit noch mehr Dimensionen versucht man irgendwann gar nicht mehr, sich diese so bildlich vorzustellen – wenn man sie denn überhaupt einsetzt.

Hier ist die Video-Version dieses Abschnitts:


Wiedergabe stellt eine Verbindung zu YouTube her.

java.lang.ArrayIndexOutOfBoundsException

Diese Fehlermeldung wirst Du sicherlich häufig sehen, wenn Du mit Arrays arbeitest. Sie sagt aus, dass Du versucht hast, auf ein Fach in dem Array zuzugreifen, dass es gar nicht gibt. Meistens ist die Ursache, dass man eine zu hohe Fachnummer verwendet. Wenn wir zum Beispiel eine der Schleifen aus dem Programm zu den eindimensionalen Arrays ein wenig verändern, erhalten wir genau diese Meldung:

for (int zaehler=0; zaehler<meineZahlen.length; zaehler=zaehler+1){
  summe = summe + meineZahlen[zaehler];
} // klappt!

for (int zaehler=0; zaehler<=meineZahlen.length; zaehler=zaehler+1){
  summe = summe + meineZahlen[zaehler];
} // klappt nicht!

Der Fehler liegt im Detail. In der zweiten Schleife steht zaehler<=meineZahlen.length , so dass im letzten Durchlauf der Schleife zaehler den Wert meineZahlen.length. Dieser Wert ist aber zu noch, da bei Arrays die Nummerierung bei 0 anfängt! Solltest Du also mal diese Fehlermeldung erhalten, ist dies eine Stelle, die Du genau untersuchen solltest. Zum Glück erhältst Du aber eine sehr genaue Angabe darüber, wo der Fehler auftritt:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2
  at Mittelwert.main(Mittelwert.java:37)

Hier tritt der Fehler in Zeile 37 auf. Es sollte auf Fach Nummer 2 zugegriffen werden, obwohl dieses nicht existiert.

Arrays sind Referenzvariablen

Besonderheiten von Referenzvariablen

Arrays sind unser erstes Beispiel für nicht primitive Datentypen in Java. Variablen, die keinen primitiven Datentyp speichern, sind in Java grundsätzlich Referenzvariablen. Sehen wir uns anhand eines Beispiels an, was das bedeutet. In diesem kleinen Programm werden drei Arrays angelegt. Anschließend wird das erste mit den anderen beiden verglichen. Die Ausgabe ist möglicherweise etwas überraschend:

public class SomeArraysAreMoreEqualThanOthers {
  
  public static void main (String [] args){
    
    int[] erstes = new int [3];
    int[] zweites = new int [3];
    int[] drittes = new int [3];
    
    erstes[0] = 2;
    erstes[1] = 3;
    erstes[2] = 5;
    
    zweites[0] = 7;
    zweites[1] = 11;
    zweites[2] = 13;
    
    drittes[0] = 2;
    drittes[1] = 3;
    drittes[2] = 5;
    
    System.out.println(erstes == zweites); // Ausgabe false
    System.out.println(erstes == drittes); // Ausgabe false
  }
  
}

Obwohl das erste und das dritte Array dieselbem Zahlen enthalten, lautet das Ergebnis beim Vergleich false. Das liegt daran, dass die Variablen erstes und zweites nicht unmittelbar das Array speichern sondern vielmehr die Speicheradresse des eigentlichen Arrays. Da es sich um zwei verschiedene Arrays handelt, sind ihre Speicheradressen verschiedenen. Genau dies sagt die Ausgabe false an dieser Stelle aus.

Wir können uns Refrenzvariblen gut mit Pfeilen veranschaulichen:

Ddrei Pfeile mit den Beschriftungen erstes, zweites, drittes zeigen auf jeweils ein Array.
Referenzvariablen kann man sich wie Pfeile vorstellen.

Die drei Variablen stellen anschaulich gesprochen jeweils einen Pfeil dar, der auf das entsprechende Array zeigt.

Wir könnten nun in unserem Programm noch eine Anweisung ergänzen:

erstes = drittes;
System.out.println(erstes == drittes); // Ausgabe true

Die Anweisung erstes = drittes; führt dazu, dass der erste Pfeil auf dasselbe Array wie der dritte zeigt:

Die Pfeile erstes und zweites zeigen jetzt aus dasselbe Array.
Zwei Variablen referenzieren dasselbe Array.

Entsprechend führt der Vergleich der beiden nun zur Ausgabe true.

Die Tatsache, dass nun erstes und drittes auf dasselbe Array verweisen, führt dazu, dass es ab nun egal ist, ob wir erstes oder drittes schreiben. Zum Beispiel:

drittes[0] = 17;
erstes[1] = 19;
drittes[2] = 23;

for (int zaehler=0; zaehler<erstes.length; zaehler=zaehler+1){
  System.out.println(erstes[zaehler]);
}

Es werden die Zahlen 17, 19 und 23 ausgegeben.

Das obere Array mit den Einträgen 2, 3, 5 kann nun nicht mehr aufgerufen werden, da es keine Variable mehr gibt, die es referenziert. Java registriert dies und sorgt dafür, dass es aus dem Speicher gelöscht wird. Genauer gesagt, sorgt der Garbage Collector dafür, dass es aus dem Speicher entfernt wird. Anders als in einigen anderen Programmiersprachen müssen wir uns darum nicht kümmern.

Vergleich von Arrays

Möchten wir tatsächlich den Inhalt zweier Arrays vergleichen, müssen wir glücklicherweise nicht mit einer Schleife sämtliche Einträge durchlaufen. Wir können den Befehl aus dem folgenden Beispiel verwenden. Der Aufbau dieses Befehls wird uns erst im Laufe der Zeit ganz klar werden. Für den Moment akzeptieren wir einfach, dass er so aussieht.

import java.util.Arrays;

public class SomeArraysAreMoreEqualThanOthers {

  public static void main (String [] args){

    int[] erstes = new int [3];
    int[] zweites = new int [3];
    int[] drittes = new int [3];

    erstes[0] = 2;
    erstes[1] = 3;
    erstes[2] = 5;

    zweites[0] = 7;
    zweites[1] = 11;
    zweites[2] = 13;

    drittes[0] = 2;
    drittes[1] = 3;
    drittes[2] = 5;

    System.out.println(Arrays.equals(erstes, zweites)); // Ausgabe false
    System.out.println(Arrays.equals(erstes, drittes)); // Ausgabe true
    
  }

}

Beachte, dass ganz am Anfang die Anweisung import java.util.Arrays; stehen muss. Einfach gesprochen sorgt diese dafür, dass wir ein paar Zusatzfunktionen von Java laden. Genaueres dazu kommt noch später.

Auch zu diesem Abschnitt gibt es einen Clip 😉


Wiedergabe stellt eine Verbindung zu YouTube her.

Eine Alternative zu Arrays mit einer variablen Länge lernen wir hier kennen. Für diese benötigen wir allerdings Kenntnisse der objektorientierten Programmierung! Daher solltest Du sie Dir vielleicht erst später ansehen.