Wiederholungsanweisung WHILE

Salopp-Deutsch werden while-, do..while- und for-Anweisungen "Schleifen" genannt.

Die While-Schleife ist der syntaktisch einfachste Typ Wiederholungsanweisung. Es gibt einige Analogien, aber natürlich auch wesentliche Differenzen zum einfachen if-Statement.

1. Zweck

Sehr oft ist ein Programmteil mehrfach hintereinander auszuführen, bis sich ein bestimmter Programm-Zustand (z.B. Variablenwert, boolscher Ausdruck, etc.) auf bestimmte Weise geändert hat.

Einfaches Beispiel: Durchlaufen aller Einzelzeichen eines Textes (mit txt.charAt(idx)) zum Zählen der enthaltenen Leerzeichen (Es muss aufgehört werden, sobald die Anzahl der Durchläufe mit jeweils um 1 erhöhtem idx der Text-Länge entspricht).

2. Aufbau

Sie hat folgenden Aufbau:
while (fortsetzungsbedingung) { /* wiederholt auszuführender Code */ }.

Sie besteht also aus dem Schlüsselwort while gefolgt von einer "Fortsetzungsbedingung" (boolean) und danach der "Rumpf" – das Kommando oder der Block (in geschwungene Klammern eingeschlossener Code), der wiederholt ausgeführt werden soll.

Wenn die Fortsetzungsbedingung bei Auswertung true ergibt, wird "Rumpf" abgearbeitet, anderenfalls wird das while-Statement beendet und mit dem ersten dahinter (nach der schließenden geschwungenen Klammer) liegenden Statement fortgefahren.

Sobald der Rumpf durchlaufen ist, wird – anders als beim if-Statement – das Statement nicht verlassen, sondern wieder zum Beginn hinaufgesprungen und die Fortsetzungsbedingung neuerlich geprüft, etc.

Ein komplettes Konstrukt sieht z.B. so aus:

// ...
int i = 0;   // Variable, die geändert werden kann, sodass
            //  Fortsetzungsbedingung irgendwann false liefert
// Vergleich i < 5 liefert je nach aktuellem Wert von i true oder false:
while (i < 5) {  // Fortsetzungsbedingung - wenn true: Rumpf ausführen,
                //  sonst zu //Nach Ende ...
    System.out.println("Wert von 'i'=" + i);
    i++;    // i um eins erhöhen (EN: increment), sodass irgendwann
           //  Fortsetzungsbedingung false wird
}   // von hier wird wieder zur while-Zeile gesprungen
   //  und Fortsetzungsbedingung geprüft
System.out.println("End-Wert von 'i'=" + i);  //NACH Ende von while
// ...

3. Analogien/Differenzen zum IF-Statement

Strukturell gibt es eine Verwandschaft mit dem einfachen if-Statement, unterschiedlich ist das Schlüsselwort: if ←→ while und der bei while am Ende IMMER erfolgende Rücksprung nach oben zur Fortsetzungsbedingung (if springt NIE zurück, beendet das Statement bei false sofort, sonst erst nach "Umweg" durch Rumpf). Nur wenn die Prüfung der Fortsetzungsbedingung false liefert, wird analog zum Verhalten von if das Statement Statement beendet und mit der Ausführung des direkt nachfolgenden Statements begonnen.

Es gibt aber KEIN Analogon zu einem else-Zweig!!!

4. Endlos-Schleife, "Abschießen" der VM zum Stoppen in BlueJ

Manchmal möchte man den selben Code-Block unbeschränkt wiederholen (z.B. der sogenannte "Main-Loop" in Grafischen User-Interfaces) - dann erzeugt man mit Absicht eine sogenannte "Endlosschleife", d.h. die Fortsetzungsbedingung wird NIE false.

In den meisten Fällen passiert dies aber unbeabsichtigt, indem man z.B. im obigem Beispiel das Inkrementieren von i vergisst.

Dann ist es gut zu wissen, wie man z.B. BlueJ stoppen kann:

Endlosschleife stoppen mit "Reset Java Virtual Machine"
Abbildung 1. Endlosschleife in BlueJ stoppen: Rechtsklick → "Reset Java Virtual Machine" auf animierten Balken (hier mit rotem "Lasso" markiert)

Möglichkeiten:

  • Links-Mausklick auf im roten "Lasso" rechts sichtbaren grauen, gekrümmtem Pfeil

  • Rechts-Mausklick auf bewegten blauen Balken im "Lasso"

  • im Menü "Tools/Reset Java Virtual Machine"

  • Tastenkombination [Ctrl] + [Shift] + [R]

5. While mit stringObj.charAt(idx)

Die while-Scheife setzt uns nun in die Lage, jedes einzelne Zeichen eines Textes mit der String-Methode stringObj.charAt(idx) zu erhalten und somit vielfältige Manipulationen mit Strings durchzuführen.

5.1. Einfaches Beispiel

Zur Demonstration geben wir vor jedem Zeichen zusätzlich ein '.' aus und schließen den Text in eckige Klammern ein. Vor der schließenden Klammer fügen wir aus Symmetriegründen ebenfalls einen Punkt ein. Der Code dazu:

    // ...
    String txt = "Hallo Leser!";
    int i = 0;
    System.out.print('[');   // Einzelnes '[' (ohne Zeilenschaltung danach)
    while (i < txt.length()) {
        System.out.print("." + txt.charAt(i));
        i++;    // Inkrement nicht vergessen, sonst Endlosschleife!
    }
    System.out.println(".]");   // Abschließendes ".]" mit Zeilenschaltung
    // ...

liefert:

[.H.a.l.l.o. .L.e.s.e.r.!.]

5.2. Beispiel mit Umwandlung Groß- auf Kleinbuchstaben

Damit die nachfolgend erzielten Effekte nachvollziehbar werden, hier einige Links zu ASCII-Code-Tabellen, Erläuterungen dazu und Links zum modernen Unicode-Standard (für die ersten 128 Codes (Dezimalzahlen 0-127) stimmen beide überein):

Wichtig sind vor allem folgende Fakten:

  • Jedes Zeichen (auch Leerzeichen, Zeilenschaltung, Tabulator etc.) wird durch einen auch als Ganzzahl interpretierbaren Wert dargestellt, man kann daher in gewissem Rahmen mit Arithmetik arbeiten.

  • Die Zeichen des englischen Alphabets (keine Umlaute) liegen in richtiger Reihenfolge nebeneinander.

  • Erst kommen die Großbuchstaben ab ASCII-Code ('A' …​ 65), dann mit einigen Zeichen Abstand die Kleinbuchstaben ('a' …​ 97).

  • Differenz zw. jeweils gleichen Groß- und Kleinbuchstaben ist 32 dezimal (=20 hexadezimal).

  • Auch die Umlaute haben im Unicode Differenz 32 zw. Groß- und Kleinbuchstaben, liegen aber in anderen Zahlenbereichen.

  • Ziffern liegen ebenfalls geordnet nebeneinander ('0' …​ 48) – wird für das folgende Beispiel jedoch nicht benötigt.

Wir nutzen nun die String-Methode txt.charAt(idx), um mit while alle Einzelzeichen von txt (für beliebige Text-Längen) "in die Hand zu bekommen" und sie – falls es sich um Kleinbuchstaben handelt (US-Alphabet + 'ä', 'ö', 'ü') – in jeweils dazugehörige Großbuchstaben zu transformieren:

public void naiveToUpper(String txt) {
    int i = 0;
    while (i < txt.length()) {
        char ch = txt.charAt(i);
        if ((ch >= 'a' && ch <= 'z')
                 || ch == 'ä' || ch == 'ö' || ch == 'ü') {  // Kleinbuchstaben incl. ä, ö, ü
            ch += 'A' - 'a';  // auch bei Umlauten gleiche Diff.(-32) zw. Groß- u. Kleinbuchst.!
        }
        System.out.print(ch);
        i++;    // Inkrement nicht vergessen, sonst Endlosschleife!
    }
    System.out.println();   // Abschließende Zeilenschaltung
}

liefert bei Aufruf: naiveToUpper("Schöne Häuser und Kühe"); die Ausgabe: SCHÖNE HÄUSER UND KÜHE

6. Beispiele zu "Zeichnen mit Zeichen"

In allen nachfolgenden Beispielen wird der Kompaktheit halber keine Eingabeprüfung gemacht. Natürlich sind negative Anzahlen im folgenden nicht sinnvoll - hier sollte eine Fehlermeldung erfolgen und die Methode beendet werden.

6.1. Waagrechte Balken

Eine zentrale Bedeutung für die folgenden Aufgaben hat die nachfolgend gezeigte Methode horizBar(…​). Diese wird in allen weiteren Beispielen für diverse Aufgaben genutzt – Einrückung (Indentation) mit Leerzeichen, horizontale Linien, "Füllungen" mit Leerzeichen etc.

Zuerst erzeugen wir eine Kombination waagrechter "Balken" durch mehrfachen Aufruf der schon oben erwähnten Methode horizBar(…​) zum Zeichnen eines waagrechten Balkens mit 3 Parametern – (1) ch für das zu verwendende Zeichen, (2) size die Anzahl der Zeichen und (3) withBr ob eine abschließende Zeilenschaltung erfolgen soll.

Die erste Zeile mit Ziffern dient lediglich zur leichteren Sichtbarkeit von Einrückungen etc.:

public void horizBar(char ch, int size, boolean withBr) {   // HTML: BR ... line-break
    int i = 0;
    while (i < size) {
        System.out.print(ch);
        i++;
    }
    if (withBr) { System.out.println(); }  // optional Zeilenschaltung am Schluss
}
// ...  Aufrufe obiger Methode zum "Zeichnen" (von einer anderen Methode aus):
    System.out.println("123456789.12345");   // zum leichteren Spaltenzählen
    horizBar(' ', 4, false);  // Einrückung mit 4 Leerzeichen
    horizBar('o', 3, false);
    horizBar('.', 5, false);
    horizBar('v', 3, true);   // mit Zeilenschaltung
    horizBar(' ', 4, false);  // Einrückung mit 4 Leerzeichen
    horizBar('^', 11, true);  // Abschlussleiste mit Zeilenschaltung
// ...

Das Ergebnis ist:

123456789.12345
    ooo.....vvv
    ^^^^^^^^^^^

6.2. "Senkrechter" Balken mit Einrückung:

Nicht sehr nützlich – hier nur der Vollständigkeit halber angeführt.

public void verticalBar(char ch, int height, int indent) {
    int i = 0;
    while (i < height) {
        horizBar(' ', indent, false);   // Einrückung (indentation): Aufruf obiger Methode
        System.out.println(ch);
        i++;
    }
}
// Aufruf von einer anderen Methode aus:
    System.out.println("12345");   // zum leichteren Spaltenzählen
    verticalBar('x', 5, 2);

Ausgegeben wird für Aufruf verticalBar('x', 5, 2); :

12345
  x
  x
  x
  x
  x

6.3. Schräger Balken nach unten rechts

public void slopedBarDownRight(char ch, int height, int indent) {
    int i = 0;
    while (i < height) {
        horizBar(' ', indent + i, false);   // Einrückung und Schräge: Aufruf Methode horizBar(...)
        System.out.println(ch);
        i++;
    }
}
// Aufruf von einer anderen Methode aus:
    System.out.println("1234567");   // zum leichteren Spaltenzählen
    slopedBarDownRight('x', 5, 2);

produziert als Ausgabe für Aufruf slopedBarDownRight('x', 5, 2);:

1234567
  x
   x
    x
     x
      x
public void slopedBarDownLeft(char ch, int height, int indent) {
    int i = 0;
    while (i < height) {
        horizBar(' ', indent + (height - 1) - i, false);   // Einrückung und Schräge:
        System.out.println(ch);
        i++;
    }
}
// Aufruf von einer anderen Methode aus:
    System.out.println("1234567");   // zum leichteren Spaltenzählen
    slopedBarDownLeft('x', 5, 2);

liefert für Aufruf slopedBarDownLeft('x', 5, 2);:

1234567
      x
     x
    x
   x
  x

6.5. Ein "Dach"

Mit folgendem Code wird eine dachartige Form erzeugt:

public void roof(int width, int indent) {  // wenn Breite ungerade, wird um 1 erhöht
    if (width%2 != 0) { width++; }
    //horizBar(' ', indent + width/2, false);
    int i = 0;
    while (i < width/2) {
        horizBar(' ', indent + (width/2 - 1) - i, false);  // Klammern um width... als Denkhilfe
        System.out.print('/');
        horizBar(' ', 2*i, false);
        System.out.println('\\');    // Verdopplung nötig, da Backslash "escape-character"
        i++;
    }
}
// Aufruf von einer anderen Methode aus:
    System.out.println("12345678");   // zum leichteren Spaltenzählen
    roof(5, 2);

Das Ergebnis ist für Aufruf roof(5, 2) oder roof(6, 2):

    /\
   /  \
  /    \
12345678

6.6. Zum Selbst-probieren: ein Haus

Durch Anwendung dieser Konzepte können viele textgrafische Formen generiert werden. Eine Aufgabe wäre z.B., folgendes Haus mit parametrisierbarer Breite zu zeichnen (zuerst die Türe weglassen, nur bei "Energieüberschuss" ergänzen):

       /\
      /  \        '\' durch [AltGr] + [ß]
     /    \
    /  _   \      Zeichen in Mitte: Unterstrich '_'
    | | |  |
    | | |  |      Position der Türe könnte z.B. immer am linken Rand
  --+-+-+--+--    oder fast in der Mitte gewählt werden

Folgendes ist zu berücksichtigen:

  • Um ein symmetrisches Dach zu erzeugen, muss die Breite eine gerade Zahl sein (breite % 2 == 0).

  • Um die Tür unterzubringen, ist eine Mindestbreite von 5 nötig.

  • Wenn z.B. die Türhöhe und Beginn der Dachschräge konstant sind, hängen Gesamthöhe und Breite direkt zusammen.

7. Arbeiten mit dem Debugger

in Arbeit …​

Nochmals kurz das Wichtigste:

  • Sicherstellen, dass Code compiliert ist (Klasse nicht schraffiert)

  • Breakpoint setzen, wo die Analyse beginnen soll

  • Ganz normal Klasse instanzieren (Objekt erzeugen mit Maus-Rechtsklick auf Klasse, dort einen der Konstruktoren aufrufen)

  • Ganz normal Methode aufrufen – diese wird bis vor die Zeile ausgeführt, in der der Breakpoint gesetzt wurde, dann wird das Debugger-Fenster geöffnet, in dem u.a. die aktuell definierten Variablen samt ihren aktuellen Werten angezeigt werden und in dem unten die Buttons [Step], [Step into], [Continue], [Terminate] angezeigt werden, die für das weitere, nun bei Bedarf schrittweise Ausführen des Codes zu benutzen sind.

  • Es wird auch die Aufruf-Verschachtelung der offenen Methoden angezeigt – hier kann durch Auswahl auch der Zustand beliebiger offener Methoden angesehen werden

Der Demo-Code in der oben verlinkten Seite enthält auch verschachtelte Methodenaufrufe.

Zur Analyse der aufgerufenen Methode gibt es mindestens 2 Möglichkeiten:

  • Setzen eines Breakpoints in der aufgerufenen Methode

  • Verwenden von [Step into] in der Zeile mit dem Methodenaufruf.

8. Verschachelte Schleifen

8.1. Überblick

Schleifen können im Rumpf weitere Schleifen enthalten. Oft stehen die beiden Schleifen in logisch engem Zusammenhang – d.h. dass z.B. der Wertebereich der Schleifen-Laufvariable der inneren Schleife von der Laufvariablen der äußeren Schleife abhängt.

8.2. Ersetzen der Methode horizBar(…​) durch "innere Schleife"

in Arbeit …​

8.3. Ausgabe einer 1x1-Tabelle

in Arbeit …​