Arrays Intro — Mehrere Werte gleichen Typs zusammen verwalten

in Arbeit ...
//TODO: Min/Max-Wert/Position-Suche, Dreieckstausch

1. Einstieg

Sehr häufig ergibt sich Bedarf an der gemeinsamen Verwaltung mehrerer gleichartiger Werte (Messwerte, Namenslisten, Personenlisten, allgemeine Objektlisten, etc.). Ein sehr plakatives Beispiel sind Texte, die man ja gut als (geordnete) Liste von Zeichen (Typ char) interpretieren kann.

Als fundamentales Konzept dafür stellt Java (wie auch sehr viele andere Programmiersprchen) Arrays bereit.

Ein Array kann einerseits als Ganzes gehandhabt werden und andererseits können die einzelnen enthaltenen Daten(-Zellen) gezielt mittels einer "Index"-Nummer (beginnend bei 0) angesprochen werden.

Arrays sind in Java als eine spezielle Art von Objekten realisiert – das wird immer wieder bei der Handhabung erkennbar.

Wie bei "normalen" Objekt-Variablen sind Array-Variable Objektreferenzen und enthalten initial den Wert null.

Hier ein Beispiel eines Integer-Arrays (man sieht, die Indices beginnen mit 0):

Integer-Array mit Kapazität/Länge = 5
Index 0 1 2 3 4

Wert

17

-12

33

0

99

Eine kompakte Zusammenfassung findet sich unten, im Anschluss daran Beispiele.

2. Deklarieren einer Array-Variablen

Arrays sind geordnete Sammlungen gleichartiger Werte.
Sie werden in Java als Objekte verwaltet, die keine üblichen Instanzvariablen haben, sondern eine bei Erzeugung festgelegte Anzahl von "Zellen", die jeweils einen Wert bestimmten Typs enthalten. Zusätzlich haben sie ein Read-Only-Attribut length, das die Kapazität (beim Erzeugen festgelegte Anzahl der Zellen) enthält.

Wie für alle anderen Datentypen gibt es auch für Arrays Variable. Diese haben als Typ den Zell-Typ gefolgt von [], womit der Wertesammlungs-Charakter symbolisiert wird.
Die Anzahl der Zellen ist KEIN Teil des Typs!
Beispiele:

  • int[] ganzzahlen …​ Sammlung von Ganzzahlen

  • float[] floatzahlen …​ Sammlung von float-Werten

  • boolean[] wahrheitswerte …​ Sammlung von ahrheitswerten

  • String[] texte …​ Sammlung von Strings

  • Person[] personen …​ Sammlung von Person-Objekten

Array-Variable können anderen Array-Variablen gleichen Typs zugewiesen werden, aber auch als Methodenparameter oder Methoden-Rückgabewert verwendet werden - z.B.:

    int[] zahlen1 = {1, 3, 99, -5};  // hier gleich mit Werten instanziert - siehe später
    int[] zahlen2 = zahlen1;  // zahlen2 jetzt anderer Name f. SELBES array! - siehe später

    float[] methode1(String txt, int[] zahlen) { ... }  // Rückgabetyp, param sind Arrays

    void nutzung() {   //
        float[] floatVals = methode1("Info", zahlen1);  // obiges Int-Array wird übergeben
        for (int i = 0; i < floatVals.length; i++) {    // Schleife über Array-Inhalt
            System.out.println("Wert " + i + ": " + floatVals[i]); // Index- und Wert-Ausgabe
        }
    }

3. Erzeugung und Zugriff auf das ganze Array

Erzeugt wird ein Array wie jedes andere Objekt häufig mit new, gefolgt vom Datentyp und der gewünschten Kapazität in eckigen Klammern:
z.B. new int[5], new char[20], new double[100] oder new String[50] (weitere Erzeugungsvarianten siehe weiter unten).
Die Zellen/Elemente werden mit dem Standard-Wert des Element-Datentyps vorbelegt (0 für Zahlen samt char, false für boolean, null für Strings und sonstige Objekte)

Alternativ kann ein Array auch durch Angabe aller Vorbelegungswerte so erzeugt werden:
new int[] {17, -12, 33, 0, 99};
Hier DARF KEINE Kapazität angegeben werden - diese ergibt sich aus der Anzahl der in geschwungenen Klammern (beistrich-getrennten) gelisteten Elemente.

Wenn Deklaration, Erzeugung und Zuweisung im SELBEN Statement erfolgt, kann new int[] weggelassen werden, da alle Infos für den Compiler vorliegen:
int[] ganzzahlen = {17, -12, 33, 0, 99}

Das gesamte Array wird wie jedes andere Objekt mittels Objektreferenz "angesprochen":

  • als "Ergebnis" der Array-Erzeugung z.B. mit new int[5]

  • in Form einer Array-Variablen – deklariert z.B. mit int[] zahlenliste (als Instanzvariable, lokale Variable oder Parameter-Variable)

  • als Rückgabewert einer Methode – z.B. mit getZahlenliste()

Sehr häufig wird das Array über eine Variable angesprochen/verwaltet. Array-Variable enthalten Objekt-Referenzen. Die Typ-Information bei der Deklaration besteht aus dem Typ der Elemente gefolgt von [] (als Symbol einer Sammlung interpretierbar).

Die Array-Variable zeigt nach Deklaration auf null und benötigt vor Verwendbarkeit die Zuweisung einer Objekt-Referenz:

int kapazitaet = 5;
int[] werteliste = new int[kapazitaet];

Damit ist Integer-Array erzeugt/instanziert und jede Zelle ist mit dem Default-Wert für den jeweiligen Datentyp gefüllt (0 für alle Zahlentypen, false für boolean, null für Strings und andere Objekttypen).

Alternativ können explizit Initialwerte festgelegt werden. KEINE Kapazitätsangabe – wird durch Anzahl der in geschwungenen Klammern beistrich-getrennt gelisteten Elemente festgelegt:

int[] werteliste = new int[] {17, -12, 33, 0, 99};

Für den häufigen Fall der kombinierten Deklaration und Instanzierung kann der Compiler den Typ sehr leicht selbst feststellen und es genügt zu schreiben:

int[] werteliste = {17, -12, 33, 0, 99};
// oder
char[] zeichenliste = {'E', 'v', 'i'};
// oder
String[] namensliste = {"Hugo", null, "Emma", "Ada"};
// oder sogar
Person emma = new Person("Emma", 2012);
Person[] personen = {new Person("Hugo", 1999), emma, new Person("Ada", 2002), null, null};

Folgendes ist beim Personen-Array beachtenswert:

Ein Objekt-Wert kann naheliegenderweise sowohl per Objektvariable als auch durch direktes Instanzieren (mit new) gesetzt werden. Auch Zuweisung von null ist in String-Arrays – und allgemein Objekt-Arrays – möglich.

4. Zugriff auf die Einzelwerte

Die einzelen "Zellen" des Arrays verhalten sich wie normale Variable – man muss lediglich zum Namen des Gesamt-Arrays in eckigen Klammern den Zell-Index angeben (beginnend mit 0):

int value = werteliste[0];  // greift lesend auf den ersten Wert (in Zelle 0) zu.
werteliste[1] = -13;        // schreibt in die zweite Zelle (Index 1) den Wert `-13`.

Im Prinzip kann man ein Array wie ein Hotel mit vielen Zimmern im Gegensatz zu einer Ferienhütte mit einem einzigen Zimmer sehen. Die Hütte hat z.B. den Namen "Karlis Kate" und das Hotel den Namen "Alpenblick". Um den Bewohner der Hütte zu finden, reicht der Hüttenname, für einen Gast im Hotel ist z.B. nötig zu sagen: Hotel Alpenblick, Zimmer 12.

Zum Durchlaufen aller Werte eines Arrays ist die FOR-Schleife wie maßgeschneidert:

    // ...
    int[] theArray = werteliste;   // Zuweisen der Objektreferenz von einer Array-Variablen zu einer anderen
    for (int i = 0; i < theArray.length; i++) {
        System.out.println("Zelle " + i + " -> Wert=" + theArray[i]);
    }
    // ...

5. Kapazitätserhöhung

Die Kapazität des Arrays wird bei der Erzeugung (Instanzierung) festgelegt und kann nicht verändert werden.

Falls ein Array zu klein wird, muss ein neues Array-Objekt mit höherer Kapazität instanziert werden, die Werte werden umkopiert und die Array-Variable erhält das neue Array zugewiesen (siehe zentrale Methode vergroessern(…​)):

public class ArrayVergroesserung
{
    public void demoKapazitaetsErhoehung() {
        int[] werteliste = {17, -12, 33};
        printArray(werteliste, "werteliste", "Array vor Vergrößern");
        // zu klein ...
        werteliste = vergroessern(werteliste, 10);
        printArray(werteliste, "werteliste", "Array nach Vergrößern");
    }

    public int[] vergroessern(int[] listeAlt, int kapazitaet) {
        if (kapazitaet <= listeAlt.length) {
            System.out.println("FEHLER in 'vergroessern(..)' - bestellte Kapazität "
                    + "gleich oder zu klein -> liefere altes Array zurück");
            return listeAlt;
        }
        int[] listeNeu = new int[kapazitaet];  // neues Array instanzieren
        for (int i = 0; i < listeAlt.length; i++) {   // umkopieren
            listeNeu[i] = listeAlt[i];
        }
        return listeNeu;  // zuletzt Objektreferenz auf das neue Array setzen
    }

    public void printArray(int[] arr, String arrName, String title) {
        System.out.println(title + " (Kapazität: " + arr.length + " Zellen):");
        for (int i = 0; i < arr.length; i++) {
            String prefix = "    ";      // Einrückung 4 Zeichen
            if (i > 0) {
                prefix = ",";
                if (i % 3 == 0) {        // immer 3 Werte in einer Zeile
                    prefix += "\n    ";  // Zeilenschaltung und wieder Einrückung
                } else {
                    prefix += " ";       // Leerzeichen hinter dem Beistrich
                }
            }
            System.out.print(prefix + arrName + "[" + i + "]=" + arr[i]);
        }
        System.out.println();
    }
}

Ausgegeben wird:

Array vor Vergrößern (Kapazität: 3 Zellen):
    werteliste[0]=17, werteliste[1]=-12, werteliste[2]=33
Array nach Vergrößern (Kapazität: 10 Zellen):
    werteliste[0]=17, werteliste[1]=-12, werteliste[2]=33,
    werteliste[3]=0, werteliste[4]=0, werteliste[5]=0,
    werteliste[6]=0, werteliste[7]=0, werteliste[8]=0,
    werteliste[9]=0

6. Zusammenfassung grundlegende Verwendung

Hier nochmals die häufigsten grundsätzlichen Verwendungs-Schritte:

  • Deklaration einer Array-Variablen:
    int[] myIntArray;
    enthält zu Beginn null (zeigt ins Leere). Die eckigen Klammern hinter dem Datentyp drücken aus, dass es sich um eine Sammlung von Integer-Werten handelt, nicht um einen Einzelwert.

  • Erzeugung eines Array-Objekts und Zuweisung:
    myIntArray = new int[5];
    Int-Array mit Platz für 5 Elemente (int-Zahlen). Nach Erzeugung enthalten die Einzelzellen den Datentyp-Standardwert (0 bei allen Zahlentypen, false bei boolean, null bei Objekten).
    WICHTIG: Bei der Erzeugung steht in den eckigen Klammern nicht der Zell-Index, sondern die festgelegte Kapazität. Zell-Indices bei Kapazität 3 sind 0, 1, 2 (Höchstwert um eins kleiner als Kapazität)

  • Schreiben eines Wertes in die erste Zelle (Index 0):
    myIntArray[0] = -17;

  • Lese-Zugriff auf erste Zelle:
    int value = myIntArray[0];

  • Abfrage der Array-Kapazität:
    int arrLen = myIntArray.length;

  • Wenn vorweg bekannt ist, welche Werte zu Beginn enthalten sein sollen, kann (ausschließlich direkt bei der Deklaration) anstelle expliziten Instanzierung eine in geschwungenen Klammern eingeschlossene, beistrichgetrennte Liste der Werte stehen:
    int[] intList = {1, -3, 99};

  • Allgemeiner kann immer stehen:
    intList = new int[] {-11, 33, 55, 77};
    Wichtig ist dabei, dass KEINE Kapazität angegeben werden DARF – diese wird beim Kompilieren automatisch festgestellt. Es ist also nicht möglich, nur die ersten Zellen zu initialisieren.

7. Verwendungsbeispiele

in Arbeit ...

7.1. Explizites Zuweisen, Ausgabe mittels Schleife

public void stringArrDemo() {
    String[] namen = new String[6];
    namen[0] = "Evi";
    namen[1] = "Udo";
    namen[2] = "Ada";
    namen[3] = "Ida";
    for (int i = 0; i < namen.length; i++) {
        String quote = (namen[i] != null) ? "\"" : "";     // Kurzform f. 'if-else'
        System.out.println("namen[" + i + "] = " + quote + namen[i] + quote + ";");
    }
    // Letzte 2 Zellen bleiben auf Default-Wert (null f. Objekte), 'quote' ist leer
}

liefert

namen[0] = "Evi";
namen[1] = "Udo";
namen[2] = "Ada";
namen[3] = "Ida";
namen[4] = null;
namen[5] = null;

Beachte:

  • Nur die ersten 4 der insgesamt 6 Zellen werden explizit gefüllt. Die letzten 2 bleiben auf dem Default-Wert, der bei der Instanzierung automatisch gesetzt wird (für Strings und alle Objekte ist dieser null)

  • beim Setzen von quote wird eine spezielle Kurzform von if-else verwendet. Diese hat – anders als das "normale" if-else – einen Rückgabewert, ist also ein Ausdruck!
    Struktur: Bedingung ? true-Wert : false-Wert, z.B.:
    Beispiel: String antwort = i < 5 ? "i kleiner 5" : "i gleich/größer als 5";
    "true-Wert" und "false-Wert" müssen "zuweisungskompatibel" zum Zuweisungs-Ziel sein!

7.2. Summe der Werte in einem Int-Array

Eine grundlegend wichtige Operation ist ds Aufsummieren der Werte eines Arrays:

public int summeVon(int[] werte) {
    int sum = 0;
    for (int i = 0; i < werte.length; i++) {
        sum += werte[i];
    }
    return sum;
}
public int runTheMethod1() {
    return summeVon(new int[]{2, 3, 1, 4});  // liefert 10 zurück
}

7.3. Durchschnittsbeechnung der Werte in einem Int-Array

Durchschnittsberechnung erfolgt durch Summieren der Werte und Division durch die Anzahl. Bei einem Array muss Klärung erfolgen, ob das Array als voll oder teilgefüllt betrachtet wird. Da es hier als voll angenommen wird, ist die Rechnung simpel: Anzahl ist Array-Länge.

Für Fälle, wo "leere" Zellen möglich sind, existieren mehrere Verfahren, damit umzugehen. Das wird uns etwas später beschäftigen.

public double durchschnittVon(int[] werte) {
    double sum = 0;                       // Summier-Var., Start mit 0
    for (int i = 0; i < werte.length; i++) {
            sum += werte[i];              // erhöhen um Wert in Zelle [i]
    }
    return sum/werte.length;
}
public double runTheMethod2() {
    return durchschnittVon(new int[]{2, 3, 1, 4});  // liefert 2.5 zurück
}

7.4. Durchschnittsberechnung mittels Methode 'summeVon(…​)'

Nun nutzen wir die oben implementierte Methode zur Summenberechnung.
Achtung: oben wird int zurückgeliefert, Array-Länge ist ebenfalls int, damit wird Integer-Division durchgeführt, was hier unbrauchbar ist. Daher muss entweder Zähler oder Nenner auf double "getrickst" werden, das kann durch Addition von 0.0 oder Multiplikation mit 1.0 erfolgen:

public double durchschnittVon2(int[] werte) {
    //return (0.0 + summeVon(werte))/werte.length;  // mind. EIN double-Wert nötig:
    return 1.0*summeVon(werte)/werte.length;        // oder so
} // addiere ^ um summeVon(..) auf double zu bringen und Int-Divis. zu verhindern
public double runTheMethod3() {
    return durchschnittVon2(new int[]{2, 3, 1, 4});  // liefert 2.5 zurück
}

7.5. Summe der Werte, die innerhalb eines Bereiches liegen

Sehr oft besteht eine Einschränking, welche Werte in die Summierung aufgenommen werden sollen/dürfen.

Hier geben wir den erlaubten Wertebereich als geschlossenes (Ober- und Untergrenze sind enthalten) Intervall an, es könnten aber auch nur ungerade Zahlen erlaubt sein, etc.:

public int summeGefiltertVon(int[] werte, int gueltigVon, int gueltigBis) {
    int sum = 0;
    for (int i = 0; i < werte.length; i++) {
        if (werte[i] >= gueltigVon && werte[i] <= gueltigBis) {
            sum += werte[i];
        }
    }
    return sum;
}
public int runTheMethod4() {
    return summeGefiltertVon(new int[]{2, 3, 1, 4}, 0, 3); // liefert 6 zurück
}

7.6. Durchschnitt von Werten, die innerhalb eines Bereiches liegen

public double durchschnittGefiltertVon(int[] werte, int gueltigVon, int gueltigBis) {
    int sum = 0;
    int anz = 0;
    for (int i = 0; i < werte.length; i++) {
        if (werte[i] >= gueltigVon && werte[i] <= gueltigBis) {
            sum += werte[i];
            anz++;
        }
    }
    //return (double)sum/anz;
    //return sum/(1.0*anz);
    //return sum/1.0/anz;
    //return sum*1.0/anz;
    return (0.0 + sum)/anz;
}

7.7. Umwandeln eines Strings in ein Char-Array

Strings und char-Arrays sind unterschiedliche Repräsentationen des selben Inhalts, können also verlustfrei ineinander übergeführt werden.

Hier wird zum Lesen eines Einzelzeichens im Text (an Position i) die String-Methode charAt(i) genutzt und das extrahierte Zeichen wird in die korrespondierende Zelle i zugewiesen.
Die nötige Kapazität für das Array ist naturgemäß genau die String-Länge:

public char[] txtToCharArr(String inTxt) {
    int txtLen = inTxt.length();
    char[] txtChars = new char[txtLen];  // new char[inTxt.length()];
    for (int i = 0; i < txtLen; i++) {
        txtChars[i] = inTxt.charAt(i);
    }
    return txtChars;
}
public char[] runTheMethod5() {
    return txtToCharArr("susi");  // liefert char-Array {'s', 'u', 's', 'i'}
}

7.8. Umwandeln eines char-Arrays in einen String

"Inverse Operation" zu obiger Methode:

public String charArrToTxt(char[] charArr) {
    String txt = "";
    for (int i = 0; i < charArr.length; i++) {
        txt = txt + charArr[i];
        // txt += charArr[i];   ... alternativ
    }
    return txt;
}
public String runTheMethod6() {
    return charArrToTxt(new char[]{'s', 'u', 's', 'i'}); // liefert String "susi"
}

7.9. Umwandeln von String nach char-Array und zurück

Mittels Nutzung obiger Methoden wird der Parameter-Text erst in ein char-Array und dann wieder zurück in einen String (gleichen Inhalts, "verlustfrei") transformiert:

public String convertTxt2ChArr2Txt(String inTxt) {
    char[] charArr = txtToCharArr(inTxt);
    String outTxt = charArrToTxt(charArr);
    return outTxt;
}
public String runTheMethod7() {
    return convertTxt2ChArr2Txt("susi");  // liefert 'nach Umwegen' wieder "susi"
}

7.10. Umdrehen eines Char-Arrays

Die Idee ist, die Buchstaben-Reihenfolge umzukehren. Aus "Evi" soll "ivE" und aus "Emil" soll "limE" werden.

Wichtig dabei ist, dass die "gegenüber liegenden" Buchstaben mittels "Dreieckstausch" ausgetauscht werden (bei ungerader Anzahl bleibt das Mittelzeichen fix).

Das Array darf dabei nur bis maximal zur Hälfte durchlaufen werden, das sonst ab der Mitte die Zeichen zurückgetauscht würden und am Ende wieder der selbe String da stünde:

public char[] reverse(char[] charArray) {
    int arrLen = charArray.length;
    int halfLen = arrLen/2;  // int-Division -> ungerade: 5 -> 2
    int maxIdx = arrLen - 1;
    for (int i = 0; i < halfLen; i++) {
        // Dreieckstausch:
        char ch = charArray[i];
        charArray[i] = charArray[maxIdx - i];
        charArray[maxIdx - i] = ch;
    }
    return charArray;
}
public String runTheMethod8() {  // Vor Rückgabe wird in String umgewandelt!
    return charArrToTxt(reverse(new char[]{'E', 'm', 'i', 'l'});
}

7.11. Ersetzen aller Vokale, Umlaute und 'y' durch den gegebenen Char-Parameter

Der Einfachheit halber wird der String zuerst in Kleinbuchstaben umgewandelt

public void vokaleZu(char toCh, String inTxt) {
    int txtLen = inTxt.length();
    char[] txtChars = new char[txtLen];  // new char[inTxt.length()];
    for (int i = 0; i < txtLen; i++) {
        char ch = inTxt.toLowerCase().charAt(i);
        if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o'
        || ch == 'u' || ch == 'ä' || ch == 'ö' || ch == 'ü' || ch == 'y') {
            ch = toCh;
        }
        txtChars[i] = ch;
    }
    System.out.println("Originaler Text: " + inTxt);
    System.out.println("Geänderter Text: " + charArrToTxt(txtChars));
}
public String runTheMethod9() {
    return vokaleZu('ü', "Ich zocke die ganze Nacht");
}