Kontrollstrukturen
Beispielhafte Probleme
Auswahlanweisungen sind in einer Programmiersprache notwendig, um einem Programm die Fähigkeit zu geben, Entscheidungen zu treffen. Ohne sie würde ein Programm immer strikt Schritt für Schritt den gleichen Ablauf verfolgen, unabhängig von den Daten oder dem Kontext.
Mit Auswahlanweisungen wie if, else if, else oder switch kann das Programm verschiedene Wege einschlagen, abhängig von bestimmten Bedingungen. Dadurch wird es flexibler, dynamischer und anpassungsfähig, beispielsweise um Benutzereingaben zu prüfen, auf Fehler zu reagieren oder unterschiedliche Fälle unterschiedlich zu behandeln.
Ein- und Zweiseitige Auswahlanweisung
- Einseitige Auswahlanweisung
- prüfe eine Bedingung, die einen boolschen Wert ergibt
- wenn Bedingung true ist führe Anweisungen im True-Zweig aus
- fahre nach der Auswahlanweisung fort
- Zweiseitige Auswahlanweisung
- prüfe eine Bedingung, die einen boolschen Wert ergibt
- wenn Bedingung true ist führe Anweisungen im True-Zweig aus
- wenn Bedingung false ist führe Anweisungen im Else-Zweig aus
- fahre nach der Auswahlanweisung fort
Der Unterschied zwischen einseitigen und zweiseitigen Auswahlanweisungen liegt darin, ob nur bei erfüllter Bedingung etwas passiert oder in beiden Fällen (egal ob die Bedingung erfüllt ist oder nicht).
Einseitige Auswahl in Java (if)
if-Anweisung bietet eine einseitige Auswahl
- Syntax:
if (BEDINGUNG) { TRUE-ZWEIG } - wenn die logische Bedingung erfüllt ist, wird der True-Zweig ausgeführt
if (antwort == 42) {
System.out.println("Die Antwort auf alle Fragen");
}
In diesem Beispiel wird der Text nur ausgegeben, wenn die Prüfung antwort == 42 den Wert true ergibt, wenn also die Variable antwort den Wert 42 enthält. Ist dies nicht der Fall, wird der Rumpf der if-Anweisung übersprungen und es geht im Programm danach weiter.
Zweiseitige Auswahl in Java (if)
if-else-Anweisung bietet eine zweiseitige Auswahl
- Syntax:
if (BEDINGUNG) { TRUE-ZWEIG } else { ELSE-ZWEIG } - wenn die logische Bedingung erfüllt ist, wird der True-Zweig ausgeführt, andernfalls der Else-Zweig
if (antwort == 42) {
System.out.println("Die Antwort auf alle Fragen");
} else {
System.out.println("Keine Antwort");
}
In diesem Beispiel wird der Text „Die Antwort auf alle Fragen“ nur ausgegeben, wenn die Prüfung antwort == 42 den Wert true ergibt, wenn also die Variable antwort den Wert 42 enthält. Ist dies nicht der Fall, wird der else-Zweig ausgeführt und es kommt die Ausgabe „keine Antwort“.
Mehrfachverzweigung mit if-else-if
if-else-if-Anweisung bietet eine Mehrfachverzweigung
- eigentlich nur eine Verschachtelung mehrerer
if-else-Anweisungen - wird speziell formatiert, um besser lesbar zu sein
- Beliebig viele
else if, nur einelse
if (BEDINGUNG1) {
TRUE-ZWEIG1
} else if (BEDINGUNG2) {
TRUE-ZWEIG2
} else if (BEDINGUNG3) {
TRUE-ZWEIG3
} else {
ELSE-ZWEIG
}
Die if-else-if-Anweisung in Java erlaubt es, mehrere Bedingungen nacheinander zu prüfen und je nach Ergebnis verschiedene Programmzweige auszuführen. Sie stellt eine Form der Mehrfachverzweigung dar und wird häufig verwendet, wenn mehr als zwei mögliche Fälle unterschieden bzw. getestet werden sollen.
Technisch gesehen ist eine if-else-if-Kette einfach eine verschachtelte Folge von if- und else-Blöcken.
Das bedeutet: Java führt die Bedingungen der Reihe nach aus und prüft jede if-Bedingung, bis eine zutrifft. Sobald eine wahre Bedingung gefunden wurde, wird der zugehörige Codeblock ausgeführt und alle weiteren Bedingungen werden ignoriert.
Auch wenn es sich um verschachtelte if-Anweisungen handelt, wird die if-else-if-Struktur in Java flach und übersichtlich formatiert:
if (bedingung1) {
// Anweisungen für Fall 1
} else if (bedingung2) {
// Anweisungen für Fall 2
} else if (bedingung3) {
// Anweisungen für Fall 3
} else {
// Anweisungen für alle übrigen Fälle
}
Diese Formatierung verbessert die Lesbarkeit und hilft dabei, die logische Struktur auf einen Blick zu erfassen – im Gegensatz zu tief verschachteltem Code, wie es das folgende Beispiel zeigt:
if (bedingung1) {
// Anweisungen für Fall 1
} else {
if (bedingung2) {
// Anweisungen für Fall 2
} else {
if (bedingung3) {
// Anweisungen für Fall 3
} else {
// Anweisungen für alle übrigen Fälle
}
}
}
if (temp < 10) {
System.out.println("Viel zu kalt");
}
else if (temp < 20) {
System.out.println("Kalt");
}
else if (temp > 50) {
System.out.println("Viel zu warm");
}
else if (temp > 30) {
System.out.println("Warm");
}
else {
System.out.println("Alles super");
}
Mehrfachverzweigung mit switch
- switch-Anweisung bietet andere Form der Mehrfachverzweigung
switch (VARIABLE) {
case WERT1:
ANWEISUNGEN;
break;
case WERT2:
ANWEISUNGEN;
break;
...
default:
ANWEISUNGEN;
}
Besonderheiten
VARIABLE: muss eine Ganzzahl oder ein String seinWERT: ein möglicher Wert der Variable als Literal oder Konstantedefault: Zweig der ausgeführt wird, wenn kein Wert vorher gepasst hat
switch (monat) {
case 2: tage = 28; break;
case 4: tage = 30; break;
case 6: tage = 30; break;
case 9: tage = 30; break;
case 11: tage = 30; break;
default: tage = 31;
}
Die Variable, die im switch-Statement verwendet wird (hier monat) muss entweder zuweisungskompatibel mit dem Typ int sein (byte, short, char, int) oder es muss sich um eine Referenz auf eine Enumeration oder einen String handeln (Enumerationen werden später eingeführt). Es ist nicht möglich, im switch-Statement Variablen von anderen Typen zu verwenden, d. h. double, float, boolean etc. sind nicht verwendbar. Der Grund hierfür liegt in der internen Implementierung von switch in der Java Virtual Machine.
Die Werte in den case-Ästen müssen Konstanten vom Typ int, String oder einer Enumeration sein. Sie brauchen keinen expliziten Block zu schreiben, d. h. anders als beim if bezieht sich der case-Ast auf alle Statements bis zum nächsten case oder break.
Der default-Ast wird ausgeführt, wenn keine der Case-Konstanten gepasst hat.
Bei Java fällt bei einem switch-Statement der Kontrollfluss durch alle case-Statements, die auf das case folgen, bei dem die Bedingung zutraf. Daher muss man immer explizit ein break; hinter die case-Statements schreiben, wenn man dieses Verhalten nicht möchte – was fast immer der Fall ist. Java hat dieses Verhalten von C geerbt, wo es ebenfalls als obskur gilt.
Da bei einem case der Konrollfluss bis zum ersten break weiterläuft, kann man das obige Beispiel auch noch kompakter schreiben als:
switch (monat) {
case 2:
tage = 28; break;
case 4:
case 6:
case 9:
case 11:
tage = 30; break;
default:
tage = 31;
}
Mit Java 14 wurde dann auch noch die Syntax des alten Switch mit : erweitert, sodass man die Konstanten durch Komma (,) getrennt schreiben kann:
Erweitertes switch (seit Java 14)
- im
casemehrere Konstanten möglich - durch
,getrennt
// new old-school switch (Java >= 14)
int tage;
switch (monat) {
case 2:
tage = 28;
break;
case 4, 6, 9, 11:
tage = 30;
break;
default:
tage = 31;
}
Man merkt switch seine Abstammung von der Programmiersprache C deutlich an. Insbesondere die Verwendung von break und das Fehlen von geschweiften Klammern für die Instruktionen, die ausgeführt werden, wirken nicht „natürlich“. Aus diesen Gründen hat man in Java 12 eine neue Form von switch eingeführt, die syntaktisch anders aussieht und mehr nach „Java schmeckt“.
switch (titel) {
case "Manager" -> {
addEinzelnesBuero();
addFirmenwagen();
addSekretaerin();
}
case "Senior" -> {
addEinzelnesBuero();
addFirmenwagen();
}
default -> addGrossraumbuero();
}
Wenn nur ein einzelnes Statement in dem Zweig vorkommt, kann man die Klammern auch weglassen, wie es hier im default-Zweig gezeigt ist.
Wenn man im switch nur einen Wert zuweisen möchte, kann man die Kontrollstruktur als Ausdruck verwenden.
// old-school switch (Java < 12)
int tage;
switch (monat) {
case 2: tage = 28; break;
case 4:
case 6:
case 9:
case 11: tage = 30; break;
default: tage = 31;
}
// Neue Form (Java >= 12)
int days = switch (monat) {
case 2 -> 28;
case 4, 6, 9, 11 -> 30;
default -> 31;
};
Problematisch wird die Zuweisung eines Wertes in der neuen Form, wenn man einen Block verwenden möchte, um noch etwas zu berechnen. Hierzu führt Java das neue Schlüsselwort yield ein, mit dem man in der neuen case-Variante etwas zurückgeben kann.
- im
casekann man einen Wert berechnen - Schlüsselwort
yieldfürcasedient zur Rückgabe des Ergebnisses
// case mit yield
int days = switch (monat) {
case 2 -> { int d = 27; d++; yield d; }
case 4, 6, 9, 11 -> 30;
default -> 31;
};
Fragezeichen-Operator
- Bedingungen mit
ifundcasekönnen nicht in Ausdrücken eingesetzt werden - Beidingung Ausdrücken über Bedingungsoperator (Fragezeichen-Operator)
- einziger Operator in Java mit drei Operanden (ternärer Operator)
- Syntax:
BEDINGUNG ? TRUE-WERT : FALSE-WERT
if (a < 0) {
b = -a;
}
else {
b = a;
}
b = a < 0 ? -a : a;
Schleifen
In Java, wie auch in allen anderen Programmiersprachen, reichen Bedingungen allein nicht aus, um komplexe Programme zu schreiben. Deshalb benötigt man zusätzlich Schleifen als Kontrollstrukturen. Bedingungen (z. B. if, else) ermöglichen es, Entscheidungen zu treffen und den Programmfluss abhängig von bestimmten Situationen zu steuern. Sie führen Code jedoch nur einmal aus, je nachdem, ob eine Bedingung erfüllt ist oder nicht. Schleifen hingegen erlauben es, Anweisungen mehrfach automatisch zu wiederholen, solange eine bestimmte Bedingung gilt oder bis sie nicht mehr gilt. So kann man z. B. Listen verarbeiten, Zahlen durchlaufen oder Benutzereingaben wiederholt abfragen, ohne den gleichen Code mehrfach schreiben zu müssen.
Beispielhafte Probleme
Eine allgemeine Definition der Schleife findet sich z. B. bei Büchel, Praktische Informatik.
G. Büchel, Praktische Informatik
Generell muss man bei den Schleifen unterscheiden, wann die Bedingung im Vergleichsausdruck (der Schleifenbedingung) geprüft wird. Dadurch egeben sich zwei grundlegende Arten von Schleifen: kopfgesteuerte Schleifen und fußgesteuerte Schleifen.
Kopfgesteuerte Schleife
nach G. Büchel, Praktische Informatik
- Werte den booleschen Ausdruck (Schleifenbedingung) aus
- Falls die Schleifenbedingung
falseliefert, beende die Schleife und fahre danach fort - Falls die Schleifenbedingung
trueliefert, führe die Anweisung (Iterationsanweisung) aus und gehe danach zu 1.
Fußgesteuerte Schleife
nach G. Büchel, Praktische Informatik
- Führe die Anweisung (Iterationsanweisung) aus
- Werte den booleschen Ausdruck (Schleifenbedingung) aus
- Falls die Schleifenbedingung
falseliefert, beende die Schleife und fahre danach fort - Falls die Schleifenbedingung
trueliefert, gehe zu 1.
while-Schleife
- while-Schleife (while loop) prüft vor jedem Durchlauf eine boolesche Bedingung (Vergleichsausdruck)
- kopfgesteuert und abweisend
- Syntax:
while (BEDINGUNG) { ANWEISUNGEN }
while (antwort < 42) {
antwort++;
}
Die while-Schleife hat die allgemeine Form:
while (Boolescher Ausdruck)
Statement oder Block;
Der Rumpf der while-Schleife wird nur betreten, wenn die Bedingung des booleschen Ausdrucks wahr ist. Wenn man eine Schleife wünscht, die mindestens einmal durchlaufen wird, muss man die do/while-Schleife wählen. Die Variable auf die getestet wird, sollte richtig initialisiert sein und man sollte nicht vergessen sie im Schleifenrumpf zu verändern, da man andernfalls in einer Endlosschleife landet.
Wenn die Anzahl der Schleifendurchläufe schon im Voraus feststeht, sollten Sie die for-Schleife verwenden. Das hier gewählte Beispiel ist also nicht optimal, da man es besser mit for implementiert hätte. while ist gut geeignet für Schleifen, bei denen währende des Durchlaufens erst festgestellt werden kann, wann der Abbruch erfolgen soll, z. B. bei linearen Suchen etc.
// Beispiel: Gib alle Zahlen von 1 bis 10 aus
int i = 1;
while (i <= 10) {
System.out.println(i);
i++;
}
// Beispiel: Berechne Summe der Zahlen von 1 bis 10
int i = 1;
int summe = 0;
while (i <= 10) {
summe += i;
i++;
}
System.out.println(summe);
do-while-Schleife
do-while-Schleife (do-loop) ist fußgesteuert (nicht abweisend)
- Syntax:
do { ANWEISUNGEN } while (BEDINGUNG);
do {
n = readNatuerlicheZahl();
} while (n >= 0);
Der Rumpf der do/while-Schleife wird immer mindestens einmal betreten, da der Test erst am Ende erfolgt – es handelt sich also um eine fußgesteuerte Schleife. Wenn man eine Schleife wünscht, die die Bedingung vorher testet, muss man die while-Schleife verwenden. Die Variable auf die getestet wird sollte richtig initialisiert sein und man sollte nicht vergessen sie im Schleifenrumpf zu verändern, da man andernfalls in einer Endlosschleife landet.
Wenn die Anzahl der Schleifendurchläufe schon im Voraus feststeht, sollten Sie die for-Schleife verwenden.
// Beispiel: Gib alle Zahlen von 1 bis 10 aus
int i = 1;
do {
System.out.println(i);
i++;
} while (i <= 10);
// Beispiel: Berechne Summe der Zahlen von 1 bis 10
int i = 1;
int summe = 0;
do {
summe += i;
i++;
} while (i <= 10);
System.out.println(summe);
for-Schleife
Die for-Schleife ist eine kopfgesteuerte Schleife, das heißt: Bevor jeder Durchlauf beginnt, wird die Schleifenbedingung geprüft. Ist die Bedingung falsch, wird der Schleifenrumpf gar nicht erst ausgeführt.
for-Schleife (for-loop) ist kopfgesteuerte (abweisende Schleife)
- bietet mehr Möglichkeiten als die while-Schleife
- Syntax:
for (INIT; BEDINGUNG; UPDATE) { ANWEISUNGEN } INIT: initialisiert Variablen (optional)BEDINGUNG: Schleife läuft, solange die Bedingung wahr istUPDATE: Operationen, die nach jedem Durchlauf durchgeführt werden
Im Vergleich zur while-Schleife ist die for-Schleife kompakter und eignet sich besonders gut, wenn:
- die Anzahl der Durchläufe im Voraus bekannt ist,
- eine Zählvariable verwendet wird,
- Initialisierung, Bedingung und Aktualisierung klar zusammengehören.
Statt diese drei Teile auf mehrere Zeilen zu verteilen (wie bei while), lassen sie sich bei for übersichtlich in einem Ausdruck kombinieren.
Die allgemeine Syntax der for-Schleife lautet:
for (INITIALISIERUNG; BEDINGUNG; AKTUALISIERUNG) {
ANWEISUNGEN
}
Die drei Steuerteile im Kopf der Schleife:
- Initialisierung: Hier wird oft eine Zählvariable deklariert und initialisiert. Dieser Teil wird nur einmal vor dem ersten Durchlauf ausgeführt. Beispiel:
int i = 0 - Bedingung: Die Schleife läuft, solange diese Bedingung wahr ist. Vor jedem Durchlauf wird sie geprüft. Beispiel:
i < 10 - Aktualisierung: Dieser Teil wird nach jedem Schleifendurchlauf ausgeführt, z. B. um die Zählvariable zu verändern. Beispiel:
i++
for (int i = 0; i < 10; i++) {
vor();
}
- Die Variable
iwird mit0initialisiert. - Solange
i < 10gilt, wird die Methodevor()ausgeführt. - Nach jedem Schleifendurchlauf wird
ium 1 erhöht. - Wenn
iden Wert10erreicht, ist die Bedingungi < 10nicht mehr erfüllt, die Schleife endet und der Rumpf wird nicht mehr ausgeführt.
Insgesamt wird vor() also 10-mal aufgerufen, für die Werte i = 0 bis i = 9.
Wie bei if kann man bei for den Block weglassen, wodurch sich die for-Schleife auf das nächste Statement bezieht. Hiervon ist aber, genauso wie bei if, dringend abzuraten, da es den Source-Code erheblich schlechter lesbar macht.
Man kann in der for-Schleife einzelne Teile weglassen. Daher sind die folgenden Schleifen korrekter Java-Code:
int i = 0;
for (; i < 10; i++) {
System.out.println("Variable außerhalb deklariert");
}
int j = 0;
for (; j < 10;) {
j++;
System.out.println("Variable ausserhalb deklariert");
System.out.println("Inkrement innerhalb");
}
for (;;) {
System.out.println("Endlosschleife");
}
Da sie aber verwirrend sind, sollte man solche Formen normalerweise nicht verwenden.
Es ist zusätzlich möglich, in der for-Schleife mehrere Initialisierungen und Veränderungen parallel durchzuführen. Hierzu dient das Komma (,) als Trenner. Aber dieses Konstrukt ist ebenfalls mit Vorsicht zu genießen:
for (i = 0, j = 0; j < 10; i++, j++) { }
// Beispiel: Gib alle Zahlen von 1 bis 10 aus
for (int i = 1; i <= 10; i++) {
System.out.println(i);
}
// Beispiel: Berechne Summe aller Zahlen von 1 bis 10
int summe = 0;
for (int i = 1; i <= 10; i++) {
summe += i;
}
System.out.println(summe);
foreach-Schleife
- Iterieren über Arrays (von Anfang bis Ende) eine sehr häufige Aufgabe in der Programmierung
// Beispiel: Berechne die Durchschnittsnote mit normalem for
double[] noten = { 1.0, 2.0, 1.3, 2.3, 4.0, 2.3 };
double summe = 0.0;
for (int i = 0; i < noten.length; i++) {
double note = noten[i];
summe += note;
}
double durchschnitt = summe / noten.length;
System.out.println(durchschnitt); // -> 2.15
foreach-Schleife
- bietet verkürzte Schreibweise für das Durchlaufen eines Arrays von Anfang bis Ende
- deklariert eine Laufvariable vom Typ der Array-Elemente, die das Array schrittweise durchläuft
- Syntax:
for (DATENTYP VARIABLE : ARRAY) { ANWEISUNGEN }
Die foreach-Schleife ist eine spezielle Schleifenform in Java, die verwendet wird, um Arrays oder andere Iterable-Datenstrukturen (wie ArrayList, Set usw.) auf besonders einfache und gut lesbare Weise zu durchlaufen.
Sie bietet eine verkürzte und elegante Schreibweise, um alle Elemente eines Arrays oder einer Collection der Reihe nach zu durchlaufen, vom ersten bis zum letzten.
Sie erspart es Programmierer:innen, sich manuell um Indexverwaltung, Längenprüfung und Zugriff per Index zu kümmern, typische Fehlerquellen bei normalen for-Schleifen.
Bei der Verwendung einer foreach-Schleife wird eine Laufvariable deklariert, die bei jedem Schleifendurchlauf nacheinander den Wert eines Elements im Array (oder in einer Sammlung) annimmt.
for (DATENTYP VARIABLE : ARRAY) {
// Anweisungen für jedes Element
}
DATENTYP: Der Typ der einzelnen Elemente im Array oder in der Sammlung (z. B.int,String,double, usw.)VARIABLE: Der Name der Laufvariablen, sie enthält jeweils das aktuelle Element.ARRAY: Das Array oder die Collection, das durchlaufen wird.- Die Anweisungen im Schleifenblock werden für jedes Element einmal ausgeführt.
Die foreach-Schleife eignet sich nur zum Lesen oder Verarbeiten der Elemente, nicht zum Verändern ihrer Position im Array. Möchte man z. B. Elemente ersetzen oder mit dem Index arbeiten, ist eine klassische for-Schleife nötig.
// Beispiel: Berechne die Durchschnittsnote mit foreach
double[] noten = { 1.0, 2.0, 1.3, 2.3, 4.0, 2.3 };
double summe = 0.0;
for (double note : noten) {
summe += note;
}
double durchschnitt = summe / noten.length;
System.out.println(durchschnitt); // -> 2.15
Die hier dargestellte foreach Schleife ist semantisch vollständig identisch mit der klassischen Variante, die einen Index verwendet, um über das Array zu iterieren.
for (int i = 0; i < noten.length; i++) {
double note = noten[i];
summe += note;
}
Der Java-Compiler erzeugt sogar aus der foreach-Schleife genau die oben dargestellte klassische Variante.
Schleifenabbruch
Aktueller Schleifendurchlauf kann abgebrochen werden
- break; – Schleife wird ganz verlassen
(Sprung an die Anweisung nach der Schleife) - continue; – beginnt sofort einen neuen Schleifendurchlauf
(Sprung in die Prüfung der Bedingung)
Wird häufig als schlechter Programmierstil angesehen. Alternativen
- boolesche Kontrollvariable als Ersatz für
break - zusätzliches
ifals Ersatz fürcontinue
// Beispiel: Suche kleinste Zahl, die durch 8 und 14 teilbar ist (mit break)
for (int i = 1; i <= 8*14; i++) {
if ((i % 8 == 0) && (i % 14 == 0)) {
System.out.println(i);
break;
}
}
// Beispiel: Suche kleinste Zahl, die durch 8 und 14 teilbar ist (ohne break)
boolean gefunden = false;
for (int i = 1; i <= 8*14 && !gefunden; i++) {
if ((i % 8 == 0) && (i % 14 == 0)) {
System.out.println(i);
gefunden = true;
}
}
An dem Beispiel sieht man, dass es die Lesbarkeit des Codes durchaus erhöhen kann, wenn man break gezielt einsetzt.