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:

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
6.4. Schräger Balken nach unten links
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.