Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Ausnahmen müssen sein
9 Geschachtelte Typen
10 Besondere Typen der Java SE
11 Generics<T>
12 Lambda-Ausdrücke und funktionale Programmierung
13 Architektur, Design und angewandte Objektorientierung
14 Java Platform Module System
15 Die Klassenbibliothek
16 Einführung in die nebenläufige Programmierung
17 Einführung in Datenstrukturen und Algorithmen
18 Einführung in grafische Oberflächen
19 Einführung in Dateien und Datenströme
20 Einführung ins Datenbankmanagement mit JDBC
21 Bits und Bytes, Mathematisches und Geld
22 Testen mit JUnit
23 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Download:

- Listings, ca. 2,7 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil 2 Imperative Sprachkonzepte
Pfeil 2.1 Elemente der Programmiersprache Java
Pfeil 2.1.1 Token
Pfeil 2.1.2 Textkodierung durch Unicode-Zeichen
Pfeil 2.1.3 Bezeichner
Pfeil 2.1.4 Literale
Pfeil 2.1.5 (Reservierte) Schlüsselwörter
Pfeil 2.1.6 Zusammenfassung der lexikalischen Analyse
Pfeil 2.1.7 Kommentare
Pfeil 2.2 Von der Klasse zur Anweisung
Pfeil 2.2.1 Was sind Anweisungen?
Pfeil 2.2.2 Klassendeklaration
Pfeil 2.2.3 Die Reise beginnt am main(String[])
Pfeil 2.2.4 Der erste Methodenaufruf: println(…)
Pfeil 2.2.5 Atomare Anweisungen und Anweisungssequenzen
Pfeil 2.2.6 Mehr zu print(…), println(…) und printf(…) für Bildschirmausgaben
Pfeil 2.2.7 Die API-Dokumentation
Pfeil 2.2.8 Ausdrücke
Pfeil 2.2.9 Ausdrucksanweisung
Pfeil 2.2.10 Erster Einblick in die Objektorientierung
Pfeil 2.2.11 Modifizierer
Pfeil 2.2.12 Gruppieren von Anweisungen mit Blöcken
Pfeil 2.3 Datentypen, Typisierung, Variablen und Zuweisungen
Pfeil 2.3.1 Primitive Datentypen im Überblick
Pfeil 2.3.2 Variablendeklarationen
Pfeil 2.3.3 Automatisches Feststellen der Typen mit var
Pfeil 2.3.4 Finale Variablen und der Modifizierer final
Pfeil 2.3.5 Konsoleneingaben
Pfeil 2.3.6 Fließkommazahlen mit den Datentypen float und double
Pfeil 2.3.7 Ganzzahlige Datentypen
Pfeil 2.3.8 Wahrheitswerte
Pfeil 2.3.9 Unterstriche in Zahlen
Pfeil 2.3.10 Alphanumerische Zeichen
Pfeil 2.3.11 Gute Namen, schlechte Namen
Pfeil 2.3.12 Keine automatische Initialisierung von lokalen Variablen
Pfeil 2.4 Ausdrücke, Operanden und Operatoren
Pfeil 2.4.1 Zuweisungsoperator
Pfeil 2.4.2 Arithmetische Operatoren
Pfeil 2.4.3 Unäres Minus und Plus
Pfeil 2.4.4 Präfix- oder Postfix-Inkrement und -Dekrement
Pfeil 2.4.5 Zuweisung mit Operation (Verbundoperator)
Pfeil 2.4.6 Die relationalen Operatoren und die Gleichheitsoperatoren
Pfeil 2.4.7 Logische Operatoren: Nicht, Und, Oder, XOR
Pfeil 2.4.8 Kurzschluss-Operatoren
Pfeil 2.4.9 Der Rang der Operatoren in der Auswertungsreihenfolge
Pfeil 2.4.10 Die Typumwandlung (das Casting)
Pfeil 2.4.11 Überladenes Plus für Strings
Pfeil 2.4.12 Operator vermisst *
Pfeil 2.5 Bedingte Anweisungen oder Fallunterscheidungen
Pfeil 2.5.1 Verzweigung mit der if-Anweisung
Pfeil 2.5.2 Die Alternative mit einer if-else-Anweisung wählen
Pfeil 2.5.3 Der Bedingungsoperator
Pfeil 2.5.4 Die switch-Anweisung bietet die Alternative
Pfeil 2.5.5 Switch-Ausdrücke
Pfeil 2.6 Immer das Gleiche mit den Schleifen
Pfeil 2.6.1 Die while-Schleife
Pfeil 2.6.2 Die do-while-Schleife
Pfeil 2.6.3 Die for-Schleife
Pfeil 2.6.4 Schleifenbedingungen und Vergleiche mit == *
Pfeil 2.6.5 Schleifenabbruch mit break und zurück zum Test mit continue
Pfeil 2.6.6 break und continue mit Marken *
Pfeil 2.7 Methoden einer Klasse
Pfeil 2.7.1 Bestandteile einer Methode
Pfeil 2.7.2 Signatur-Beschreibung in der Java-API
Pfeil 2.7.3 Aufruf einer Methode
Pfeil 2.7.4 Methoden ohne Parameter deklarieren
Pfeil 2.7.5 Statische Methoden (Klassenmethoden)
Pfeil 2.7.6 Parameter, Argument und Wertübergabe
Pfeil 2.7.7 Methoden vorzeitig mit return beenden
Pfeil 2.7.8 Nicht erreichbarer Quellcode bei Methoden *
Pfeil 2.7.9 Methoden mit Rückgaben
Pfeil 2.7.10 Methoden überladen
Pfeil 2.7.11 Gültigkeitsbereich
Pfeil 2.7.12 Vorgegebener Wert für nicht aufgeführte Argumente *
Pfeil 2.7.13 Rekursive Methoden *
Pfeil 2.7.14 Die Türme von Hanoi *
Pfeil 2.8 Zum Weiterlesen
 

Zum Seitenanfang

2.6    Immer das Gleiche mit den Schleifen Zur vorigen ÜberschriftZur nächsten Überschrift

Schleifen dienen dazu, bestimmte Anweisungen immer wieder abzuarbeiten. Zu einer Schleife gehören die Schleifenbedingung und der Rumpf. Die Schleifenbedingung, ein boolescher Ausdruck, entscheidet darüber, unter welcher Bedingung die Wiederholung ausgeführt wird. Abhängig von der Schleifenbedingung kann der Rumpf mehrmals ausgeführt werden. Dazu wird bei jedem Schleifendurchgang die Schleifenbedingung geprüft. Das Ergebnis entscheidet, ob der Rumpf ein weiteres Mal durchlaufen (true) oder die Schleife beendet wird (false). Java bietet vier Typen von Schleifen:

Schleifentyp

Syntax

while-Schleife

while ( Bedingung ) Anweisung

do-while-Schleife

do Anweisung while ( Bedingung );

einfache for-Schleife

for ( Initialisierung; Bedingung; Fortschaltung )

  Anweisung

erweiterte for-Schleife (auch for-each-Loop genannt)

for ( Variablentyp variable : Sammlung )

  Anweisung

Tabelle 2.14    Die vier Schleifentypen in Java

Die ersten drei Schleifentypen erklären die folgenden Abschnitte, während die erweiterte for-Schleife nur bei Sammlungen nötig ist und daher später bei Arrays (siehe Kapitel 4, »Arrays und ihre Anwendungen«) und dynamischen Datenstrukturen (siehe Kapitel 17, »Einführung in Datenstrukturen und Algorithmen«) Erwähnung findet.

 

Zum Seitenanfang

2.6.1    Die while-Schleife Zur vorigen ÜberschriftZur nächsten Überschrift

Die while-Schleife ist eine abweisende Schleife, die vor jedem Schleifeneintritt die Schleifenbedingung prüft. Ist die Bedingung wahr, führt sie den Rumpf aus, andernfalls beendet sie die Schleife. Wie bei if muss auch bei den while-Schleifen der Typ der Bedingung boolean sein.[ 93 ](Wir hatten das Thema bei if schon angesprochen: In C(++) ließe sich while ( i ) schreiben, was in Java while ( i != 0 ) wäre. )

Vor jedem Schleifendurchgang wird der Ausdruck neu ausgewertet, und ist das Ergebnis true, so wird der Rumpf ausgeführt. Die Schleife ist beendet, wenn das Ergebnis false ist. Ist die Bedingung schon vor dem ersten Eintritt in den Rumpf nicht wahr, so wird der Rumpf erst gar nicht durchlaufen.

[zB]  Beispiel

Zähle von 100 bis 40 in Zehnerschritten herunter:

Listing 2.25    src/main/java/WhileLoop.java, main()

int cnt = 100;

while ( cnt >= 40 ) {

System.out.printf( "Ich erblickte das Licht der Welt " +

"in Form einer %d-Watt-Glühbirne.%n", cnt );

cnt -= 10;

}
[»]  Hinweis

Wird innerhalb des Schleifenkopfs schon alles Interessante erledigt, so muss trotzdem eine Anweisung folgen. Dies ist der passende Einsatz für die leere Anweisung ; oder den leeren Block {}.

while ( Files.notExists( Paths.get( "dump.bin" ) ) )

;

Existiert die Datei nicht, liefert notExists(…) die Rückgabe true, die Schleife läuft weiter, und es folgt sofort ein neuer Existenztest. Existiert die Datei, ist die Rückgabe false, und dies läutet das Ende der Schleife ein. Ein Tipp an dieser Stelle: Anstatt direkt zum nächsten Dateitest überzugehen, sollte eine kurze Verzögerung eingebaut werden.

Endlosschleifen

Ist die Bedingung einer while-Schleife immer wahr, dann handelt es sich um eine Endlosschleife. Die Konsequenz ist, dass die Schleife endlos wiederholt wird:

Listing 2.26    src/main/java/WhileTrue.java

public class WhileTrue {

public static void main( String[] args ) {

while ( true ) {

// immer wieder und immer wieder

}

}

}

Endlosschleifen bedeuten normalerweise das Aus für jedes Programm. Doch es gibt Hilfe! Aus dieser Endlosschleife können wir mittels break entkommen; das schauen wir uns in Abschnitt 2.6.5, »Schleifenabbruch mit break und zurück zum Test mit continue«, genauer an. Genau genommen beenden aber auch nicht abgefangene Exceptions oder auch System.exit(int) die Programme.

Die Ansicht »Console« mit der roten Schaltfläche zum Beenden von Programmen

Abbildung 2.8    Die Ansicht »Console« mit der roten Schaltfläche zum Beenden von Programmen

inline image  In Eclipse lassen sich Programme von außen beenden. Dazu bietet die Ansicht Console eine rote Schaltfläche in Form eines Quadrats, die nach der Aktivierung jedes laufende Programm beendet.

 

Zum Seitenanfang

2.6.2    Die do-while-Schleife Zur vorigen ÜberschriftZur nächsten Überschrift

Dieser Schleifentyp ist eine annehmende Schleife, da do-while die Schleifenbedingung erst nach jedem Schleifendurchgang prüft. Bevor es zum ersten Test kommt, ist der Rumpf schon einmal durchlaufen worden. Der Schleifentyp hilft uns bei unserem Zahlenratespiel perfekt, denn es gibt ja mindestens einen Durchlauf mit einer Eingabe, und nur dann, wenn der Benutzer eine falsche Zahl eingibt, soll der Rumpf wiederholt werden.

Listing 2.27    src/main/java/TheFinalGuess.java

public class TheFinalGuess {



public static void main( String[] args ) {

int number = (int) (Math.random() * 5 + 1);

int guess;



do {

System.out.println( "Welche Zahl denke ich mir zwischen 1 und 5?" );

guess = new java.util.Scanner( System.in ).nextInt();



if ( number == guess )

System.out.println( "Super getippt!" );

else if ( number > guess )

System.out.println( "Nee, meine Zahl ist größer als deine!" );

else // number < guess

System.out.println( "Nee, meine Zahl ist kleiner als deine!" );

}

while ( number != guess );

}

}

Es ist wichtig, auf das Semikolon hinter der while-Anweisung zu achten. Liefert die Bedingung ein true, so wird der Rumpf erneut ausgeführt.[ 94 ](Das ist in Pascal und Delphi anders. Hier läuft eine Schleife der Bauart repeat until Bedingung (das Gegenstück zu Javas do-while) so lange, bis die Bedingung wahr wird, und bricht dann ab. Ist die Bedingung nicht erfüllt, also falsch, geht es weiter mit einer Wiederholung. Ist in Java die Bedingung nicht erfüllt, bedeutet dies das Ende der Schleifendurchläufe; das ist also genau das Gegenteil. Die Schleife vom Typ while Bedingung do in Pascal und Delphi entspricht aber genau der while-Schleife in Java. ) Andernfalls wird die Schleife beendet, und das Programm wird mit der nächsten Anweisung nach der Schleife fortgesetzt. Interessant ist das Detail, dass wir die Variable guess nun außerhalb des do-while-Blocks deklarieren müssen, da eine im Schleifenblock deklarierte Variable für den Wiederholungstest in while nicht sichtbar ist. Auch weiß der Compiler, dass der do-while-Block mindestens einmal durchlaufen wird und guess auf jeden Fall initialisiert wird; der Zugriff auf nicht initialisierte Variablen ist verboten und wird vom Compiler als Fehler angesehen.

Äquivalenz einer while- und einer do-while-Schleife *

Die do-while-Schleife wird seltener gebraucht als die while-Schleife. Dennoch lassen sich beide ineinander überführen. Zunächst der erste Fall: Wir ersetzen eine while-Schleife durch eine do-while-Schleife:

while ( Ausdruck )

Anweisung

Führen wir uns noch einmal vor Augen, was hier passiert: In Abhängigkeit vom Ausdruck wird der Rumpf ausgeführt. Da zunächst ein Test kommt, wäre die do-while-Schleife schon eine Blockausführung weiter. So fragen wir in einem ersten Schritt mit einer if-Anweisung ab, ob die Bedingung wahr ist oder nicht. Wenn ja, dann lassen wir den Programmcode in einer do-while-Schleife abarbeiten.

Die äquivalente do-while-Schleife sieht wie folgt aus:

if ( Ausdruck )

do

Anweisung

while ( Ausdruck ) ;

Nun der zweite Fall: Wir ersetzen die do-while-Schleife durch eine while-Schleife:

do

Anweisung

while ( Ausdruck ) ;

Da zunächst die Anweisungen ausgeführt werden und anschließend der Test, schreiben wir für die while-Variante die Ausdrücke einfach vor den Test. So ist sichergestellt, dass diese zumindest einmal abgearbeitet werden:

Anweisung

while ( Ausdruck )

Anweisung
 

Zum Seitenanfang

2.6.3    Die for-Schleife Zur vorigen ÜberschriftZur nächsten Überschrift

Die for-Schleife ist eine spezielle Variante einer while-Schleife und wird typischerweise zum Zählen benutzt. Genauso wie while-Schleifen sind for-Schleifen abweisend: Der Rumpf wird erst dann ausgeführt, wenn die Bedingung wahr ist.

[zB]  Beispiel

Gib die Zahlen von 1 bis 10 auf dem Bildschirm aus:

Listing 2.28    src/main/java/ForLoop.java, main()

for ( int i = 1; i <= 10; i++ )            // i ist Schleifenzähler

System.out.println( i );

Eine genauere Betrachtung der Schleife zeigt die unterschiedlichen Segmente:

  • Initialisierung der Schleife: Der erste Teil der for-Schleife ist ein Ausdruck wie i = 1, der vor der Durchführung der Schleife genau einmal ausgeführt wird. Der Ausdruck initialisiert die Variable i, das Ergebnis wird dann aber verworfen. Tritt in der Auswertung ein Fehler auf, so wird die Abarbeitung unterbrochen, und die Schleife kann nicht vollständig ausgeführt werden. Der erste Teil kann lokale Variablen deklarieren und initialisieren. Diese Zählvariable ist dann außerhalb des Blocks nicht mehr gültig.[ 95 ](Im Gegensatz zu C++ ist das Verhalten klar definiert, und es gibt kein Hin und Her. In C++ implementierten Compilerbauer die Variante einmal so, dass die Variable nur im Block galt, andere interpretierten die Sprachspezifikation so, dass sie auch außerhalb gültig blieb. Die aktuelle C++-Definition schreibt nun vor, dass die Variable außerhalb des Blocks nicht mehr gültig ist. Da es jedoch noch alten Programmcode gibt, haben viele Compilerbauer eine Option eingebaut, mit der das Verhalten der lokalen Variablen bestimmt werden kann. ) Es darf keine lokale Variable mit dem gleichen Namen geben.

  • Schleifentest/Schleifenbedingung: Der mittlere Teil, wie i <= 10, wird vor dem Durchlaufen des Schleifenrumpfs – also vor jedem Schleifeneintritt – getestet. Ergibt der Ausdruck false, wird die Schleife nicht bzw. kein weiteres Mal durchlaufen und beendet. Das Ergebnis muss, wie bei einer while-Schleife, vom Typ boolean sein. Ist kein Test angegeben, so ist das Ergebnis automatisch true.

  • Schleifen-Inkrement durch einen Fortschaltausdruck: Der letzte Teil, wie i++, wird immer am Ende jedes Schleifendurchlaufs, aber noch vor dem nächsten Schleifeneintritt ausgeführt. Das Ergebnis wird nicht weiter verwendet. Ergibt die Bedingung des Tests true, dann befindet sich beim nächsten Betreten des Rumpfs der veränderte Wert im Rumpf.

Betrachten wir das Beispiel, so ist die Auswertungsreihenfolge folgender Art:

  1. Initialisiere i mit 1.

  2. Teste, ob i <= 10 gilt.

  3. Ergibt sich true, dann führe den Block aus, sonst ist es das Ende der Schleife.

  4. Erhöhe i um 1.

  5. Gehe zu Schritt 2.

Schleifenzähler

Wird die for-Schleife zum Durchlaufen einer Variablen genutzt, so heißt der Schleifenzähler entweder Zählvariable oder Laufvariable.

Wichtig sind die Initialisierung und die korrekte Abfrage am Ende. Schnell läuft die Schleife einmal zu oft durch und führt so zu falschen Ergebnissen. Die Fehler bei der Abfrage werden auch Off-by-one-Errors genannt, wenn zum Beispiel statt <= der Operator < steht. Dann nämlich läuft die Schleife nur bis 9. Ein anderer Name für den Schleifenfehler lautet Fencepost-Error (»Zaunpfahl-Fehler«). Es geht um die Frage, wie viele Pfähle für einen 100 m langen Zaun nötig sind, sodass alle Pfähle einen Abstand von 10 m haben: 9, 10 oder 11?

Wann for- und wann while-Schleife?

Da sich die while- und die for-Schleife sehr ähnlich sind, ist die Frage berechtigt, wann die eine und wann die andere zu nutzen ist. Leider verführt die kompakte for-Schleife sehr schnell zu einer Überladung. Manche Programmierer packen gerne alles in den Schleifenkopf hinein, und der Rumpf besteht nur aus einer leeren Anweisung. Dies ist ein schlechter Stil und sollte vermieden werden.

for-Schleifen sollten immer dann benutzt werden, wenn eine Variable um eine konstante Größe erhöht wird. Tritt in der Schleife keine Schleifenvariable auf, die inkrementiert oder dekrementiert wird, sollte eine while-Schleife genutzt werden. Eine do-while-Schleife sollte dann eingesetzt werden, wenn die Abbruchbedingung erst am Ende eines Schleifendurchlaufs ausgewertet werden kann. Auch sollte die for-Schleife dort eingesetzt werden, wo sich alle drei Ausdrücke im Schleifenkopf auf dieselbe Variable beziehen. Vermieden werden sollten unzusammenhängende Ausdrücke im Schleifenkopf. Der schreibende Zugriff auf die Schleifenvariable im Rumpf ist eine schlechte Idee, wenn sie auch gleichzeitig im Kopf modifiziert wird – das ist schwer zu durchschauen und kann leicht zu Endlosschleifen führen.

Die for-Schleife ist nicht auf einen bestimmten Typ festgelegt, auch wenn for-Schleifen für das Hochzählen den impliziten Typ int suggerieren. Der Initialisierungsteil kann alles Mögliche vorbelegen, ob int, double oder eine Referenzvariable. Die Bedingung kann alles Erdenkliche testen, nur das Ergebnis muss hier ein boolean sein.

Eine for-Schleife muss keine Zählschleife sein

Die for-Schleife zeigt kompakt im Kopf alle wesentlichen Informationen, ist aber nicht auf das Hochzählen von Werten beschränkt. Sie ist vielmehr dann eine gute Option, wenn es eine Variable gibt, deren Zustand in jeder Iteration verändert wird, und wenn der Abbruch irgendwie abhängig von der Variablen ist.

Eine Endlosschleife mit for

Da alle drei Ausdrücke im Kopf der Schleife optional sind, können sie weggelassen werden, und es ergibt sich eine Endlosschleife. Diese Schreibweise ist somit mit while(true) semantisch äquivalent:

for ( ; ; )

;

Die trennenden Semikola dürfen nicht verschwinden. Falls in der for-Schleife keine Schleifenbedingung angegeben ist, ist der Ausdruck immer wahr. Es folgt keine Initialisierung und keine Auswertung des Fortschaltausdrucks.

Geschachtelte Schleifen

Schleifen, und das gilt insbesondere für for-Schleifen, können verschachtelt werden. Syntaktisch ist das auch logisch, da sich innerhalb des Schleifenrumpfs beliebige Anweisungen aufhalten dürfen. Um fünf Zeilen von Sternchen auszugeben, wobei in jeder Zeile immer ein Stern mehr erscheinen soll, schreiben wir:

Listing 2.29    src/main/java/Superstar.java, main()

for ( int i = 1; i <= 5; i++ ) {

for ( int j = 1; j <= i; j++ )

System.out.print( '*' );

System.out.println();

}

Als besonderes Element ist die Abhängigkeit des Schleifenzählers j von i zu werten. Hier folgt die Ausgabe:

*

**

***

****

*****

Die übergeordnete Schleife nennt sich äußere Schleife, die untergeordnete innere Schleife. In unserem Beispiel zählt die äußere Schleife mit i die Zeilen, und die innere Schleife gibt die Sternchen in eine Zeile aus, ist also für die Spalte verantwortlich.

Da Schleifen beliebig tief verschachtelt werden können, muss besonderes Augenmerk auf die Laufzeit gelegt werden. Die inneren Schleifen werden mit ihren Durchläufen immer so oft ausgeführt, wie die äußere Schleife durchlaufen wird.

for-Schleifen und mit Komma Ausdrucksanweisungen hintereinandersetzen *

Im ersten und letzten Teil einer for-Schleife lässt sich ein Komma einsetzen, um mehrere Ausdrucksanweisungen hintereinanderzusetzen. Damit lassen sich entweder mehrere Variablen gleichen Typs deklarieren – wie wir es schon kennen – oder mehrere Ausdrücke nebeneinanderschreiben, aber keine beliebigen Anweisungen oder sogar andere Schleifen.

Mit den Variablen i und j können wir auf diese Weise eine kleine Multiplikationstabelle aufbauen:

for ( int i = 1, j = 9; i <= j; i++, j-- )

System.out.printf( "%d * %d = %d%n", i, j, i*j );

Dann ist die Ausgabe:

1 * 9 = 9

2 * 8 = 16

3 * 7 = 21

4 * 6 = 24

5 * 5 = 25

Ein weiteres Beispiel mit komplexerer Bedingung wäre das folgende, das vor dem Schleifendurchlauf den Startwert für die Variablen x und y initialisiert, dann x und y heraufsetzt und die Schleife so lange ausführt, bis x und y beide 10 sind:

int x, y;

for ( x = initX(), y = initY(), x++, y++;

x < 10 || y < 10;

x += xinc(), y += yinc() )

{

// …

}
[+]  Tipp

Komplizierte for-Schleifen werden dadurch lesbarer, dass die drei for-Teile in getrennten Zeilen stehen.

 

Zum Seitenanfang

2.6.4    Schleifenbedingungen und Vergleiche mit == * Zur vorigen ÜberschriftZur nächsten Überschrift

Eine Schleifenabbruchbedingung kann ganz unterschiedlich aussehen. Beim Zählen ist es häufig der Vergleich auf einen Endwert. Oft steht an dieser Stelle ein absoluter Vergleich mit ==, der aus zwei Gründen problematisch werden kann.

[»]  Frage

Das Programm zählt bis 10, oder?

int input = new java.util.Scanner( System.in ).nextInt();

for ( int i = input; i != 11; i++ )

System.out.println( i );

Ist der Wert der Variablen i kleiner als 11, so haben wir beim Zählen kein Problem, denn dann ist anschließend spätestens bei 11 Schluss und die Schleife bricht ab. Kommt der Wert aber aus einer unbekannten Quelle und ist er echt größer als 11, so ist die Bedingung ebenso wahr und der Schleifenrumpf wird ziemlich lange durchlaufen – genau genommen so weit, bis wir durch einen Überlauf wieder bei 0 beginnen und dann auch bei 11 und dem Abbruch landen. Die Absicht war sicherlich eine andere. Die Schleife sollte nur so lange zählen, wie i kleiner 11 ist, und nicht einfach nur ungleich 11. Daher passt Folgendes besser:

int input = new java.util.Scanner( System.in ).nextInt();

for ( int i = input; i < 11; i++ ) // Zählt immer nur bis 10 oder gar nicht

System.out.println( i );

Jetzt rennt der Interpreter bei Zahlen größer 11 nicht endlos weiter, sondern stoppt die Schleife sofort ohne Durchlauf.

Rechenungenauigkeiten sind nicht des Programmierers Freund

Das zweite Problem ergibt sich bei Fließkommazahlen. Es ist sehr problematisch, echte Vergleiche zu fordern:

double d = 0.0;

while ( d != 1.0 ) { // Achtung! Problematischer Vergleich!

d += 0.1;

System.out.println( d );

}

Lassen wir das Programmsegment laufen, so sehen wir, dass die Schleife hurtig über das Ziel hinausschießt:

0.1

0.2

0.30000000000000004

0.4

0.5

0.6

0.7

0.7999999999999999

0.8999999999999999

0.9999999999999999

1.0999999999999999

1.2

1.3

Und das so lange, bis das Auge müde wird …

Bei Fließkommawerten bietet es sich daher immer an, mit den relationalen Operatoren <, >, <= oder >= zu arbeiten.

Eine zweite Möglichkeit neben dem echten Kleiner/Größer-Vergleich ist, eine erlaubte Abweichung (Delta) zu definieren. Mathematiker bezeichnen die Abweichung von zwei Werten mit dem griechischen Kleinbuchstaben Epsilon. Wenn wir einen Vergleich von zwei Fließkommazahlen anstreben und bei einem Gleichheitsvergleich eine Toleranz mitbetrachten wollen, so schreiben wir einfach:

if ( Math.abs(x - y) <= epsilon )

Epsilon ist die erlaubte Abweichung. Math.abs(x) berechnet von einer Zahl x den Absolutwert.

Wie Bereichsangaben schreiben? *

Für Bereichsangaben der Form a >= 23 && a <= 42 empfiehlt es sich, den unteren Wert in den Vergleich einzubeziehen, den Wert für die obere Grenze jedoch nicht (inklusive untere Grenzen und exklusive obere Grenzen). Für unser Beispiel, in dem a im Intervall bleiben soll, ist Folgendes besser: a >= 23 && a < 43. Das gilt für Fließkommazahlen wie für Ganzzahlen. Die Begründung dafür ist einleuchtend:

  • Die Größe des Intervalls ist die Differenz aus den Grenzen.

  • Ist das Intervall leer, so sind die Intervallgrenzen gleich.

  • Die linkere untere Grenze ist nie größer als die rechtere obere Grenze.

[»]  Hinweis

Die Standardbibliothek verwendet diese Konvention auch durchgängig, etwa im Fall von substring(…) bei String-Objekten oder subList(…) bei Listen oder bei der Angabe von Array-Indexwerten.

Die Vorschläge können für normale Schleifen mit Vergleichen übernommen werden. So ist eine Schleife mit zehn Durchgängen besser in der Form

for ( i = 0; i < 10; i++ )              // Besser

formuliert als in der semantisch äquivalenten Form:

for ( i = 0; i <= 9; i++ )              // Nicht so gut

for ( i = 1; i <= 10; i++ ) // Auch nicht so gut
 

Zum Seitenanfang

2.6.5    Schleifenabbruch mit break und zurück zum Test mit continue Zur vorigen ÜberschriftZur nächsten Überschrift

Eine break-Anweisung innerhalb einer for-, while- oder do-while-Schleife beendet den Schleifendurchlauf, und die Abarbeitung wird bei der ersten Anweisung nach der Schleife fortgeführt.

Dass eine Endlosschleife mit break beendet werden kann, ist nützlich, wenn eine Bedingung eintritt, die das Ende der Schleife bestimmt. Das lässt sich prima auf unser Zahlenratespiel übertragen:

Listing 2.30    src/main/java/GuessWhat.java

public class GuessWhat {



public static void main( String[] args ) {

int number = (int) (Math.random() * 5 + 1);



while ( true ) {

System.out.println( "Welche Zahl denke ich mir zwischen 1 und 5?" );

int guess = new java.util.Scanner( System.in ).nextInt();



if ( number == guess ) {

System.out.println( "Super getippt!" );

break; // Ende der Schleife

}

else if ( number > guess )

System.out.println( "Nee, meine Zahl ist größer als deine!" );

else if ( number < guess )

System.out.println( "Nee, meine Zahl ist kleiner als deine!" );

}

}

}

Die Fallunterscheidung stellt fest, ob der Benutzer noch einmal in einem weiteren Schleifendurchlauf neu raten muss oder ob der Tipp richtig war; dann beendet die break-Anweisung den Spuk.

[+]  Tipp

Da ein kleines break schnell im Programmtext verschwindet, seine Bedeutung aber groß ist, sollte ein kleiner Hinweis auf diese Anweisung gesetzt werden.

Flaggen oder break

break lässt sich gut verwenden, um aus einer Schleife vorzeitig auszubrechen, ohne Flags zu benutzen. Dazu ein Beispiel dafür, was vermieden werden sollte:

boolean endFlag = false;

do {

if ( Bedingung ) {



endFlag = true;

}

} while ( AndereBedingung && ! endFlag );

Stattdessen schreiben wir:

do {

if ( Bedingung ) {



break;

}

} while ( AndereBedingung );

Die alternative Lösung stellt natürlich einen Unterschied dar, wenn nach dem if noch Anweisungen in der Schleife stehen.

Neudurchlauf mit continue

Innerhalb einer for-, while- oder do-while-Schleife lässt sich eine continue-Anweisung einsetzen, die nicht wie break die Schleife beendet, sondern zum Schleifenkopf zurückgeht. Nach dem Auswerten des Fortschaltausdrucks wird im nächsten Schritt erneut geprüft, ob die Schleife weiter durchlaufen werden soll. Ein häufiges Einsatzfeld sind Schleifen, die im Rumpf immer wieder Werte so lange holen und testen, bis diese für die Weiterverarbeitung geeignet sind.

Dazu ein Beispiel, wieder mit dem Ratespiel. Dem Benutzer wird bisher mitgeteilt, dass er nur Zahlen zwischen 1 und 5 (inklusive) eingeben soll, aber wenn er –1234567 eingibt, ist das auch egal. Das wollen wir ändern, indem wir einen Test vorschalten, der zurück zur Eingabe führt, wenn der Wertbereich falsch ist. continue hilft uns dabei, zurück zum Anfang des Blocks zu kommen, und der beginnt mit einer neuen Eingabeaufforderung.

Listing 2.31    src/main/java/GuessRight.java

public class GuessRight {



public static void main( String[] args ) {

int number = (int) (Math.random() * 5 + 1);

while ( true ) {

System.out.println( "Welche Zahl denke ich mir zwischen 1 und 5?" );

int guess = new java.util.Scanner( System.in ).nextInt();



if ( guess < 1 || guess > 5 ) {

System.out.println( "Nur Zahlen zwischen 1 und 5!" );

continue;

}



if ( number == guess ) {

System.out.println( "Super getippt!" );

break; // Ende der Schleife

}

else if ( number > guess )

System.out.println( "Nee, meine Zahl ist größer als deine!" );

else if ( number < guess )

System.out.println( "Nee, meine Zahl ist kleiner als deine!" );

}

}

}

Manche Programmstücke sind aber ohne continue lesbarer. Ein continue am Ende einer if-Abfrage kann durch einen else-Teil bedeutend klarer gefasst werden. Zunächst das schlechte Beispiel:

while ( Bedingung ) {       // Durch continue verzuckert

if ( AndereBedingung ) {

// Code, Code, Code

continue;

}

// Weiterer schöner Code

}

Viel deutlicher ist:

while ( Bedingung ) {

if ( AndereBedingung ) {

// Code, Code, Code

}

else {

// Weiterer schöner Code

}

}
 

Zum Seitenanfang

2.6.6    break und continue mit Marken * Zur vorigen ÜberschriftZur nächsten Überschrift

Obwohl das Schlüsselwort goto in der Liste der reservierten Wörter auftaucht, erlaubt Java keine beliebigen Sprünge, und goto ist ohne Funktionalität. Allerdings lassen sich in Java Anweisungen – oder ein Block, der eine besondere Anweisung ist – markieren. Ein Grund für die Einführung von Markierungen ist der, dass break bzw. continue mehrdeutig ist:

  • Wenn es zwei ineinander verschachtelte Schleifen gibt, würde ein break in der inneren Schleife nur die innere abbrechen. Was ist jedoch, wenn die äußere Schleife beendet werden soll? Das Gleiche gilt für continue, wenn die äußere Schleife fortgesetzt werden soll und nicht die innere.

  • Nicht nur Schleifen nutzen das Schlüsselwort break, sondern auch die switch-Anweisung. Was ist, wenn eine Schleife eine switch-Anweisung enthält, jedoch nicht der lokale case-Zweig mit break beendet werden soll, sondern die ganze Schleife mit break abgebrochen werden soll?

Die Sprachdesigner von Java haben sich dazu entschlossen, Markierungen einzuführen, sodass break und continue die markierte Anweisung entweder verlassen oder wieder durchlaufen können. Falsch eingesetzt, können sie natürlich zu Spaghetti-Code wie aus der Welt der unstrukturierten Programmiersprachen führen. Doch als verantwortungsvolle Java-Programmierer werden wir das Feature natürlich nicht missbrauchen.

break mit einer Marke für Schleifen

Betrachten wir ein erstes Beispiel mit einer Marke (engl. label), in dem break nicht nur aus der inneren Teufelsschleife ausbricht, sondern aus der äußeren gleich mit. Marken werden definiert, indem ein Bezeichner mit Doppelpunkt abgeschlossen und vor eine Anweisung gesetzt wird – die Anweisung wird damit markiert wie eine Schleife:

Listing 2.32    src/main/java/BreakAndContinueWithLabels.java, main()

heaven:

while ( true ) {

hell:

while ( true )

break /* continue */ heaven;

// System.out.println( "hell" );

}

System.out.println( "heaven" );

Ein break ohne Marke in der inneren while-Schleife beendet nur die innere Wiederholung, und ein continue würde zur Fortführung dieser inneren while-Schleife führen. Unser Beispiel zeigt die Anwendung einer Marke hinter den Schlüsselwörtern break und continue.

Das Beispiel benutzt die Marke hell nicht, und die Zeile mit der Ausgabe »hell« ist bewusst auskommentiert, denn sie ist nicht erreichbar und würde andernfalls zu einem Compilerfehler führen. Dass die Anweisung nicht erreichbar ist, ist klar, denn mit einem break heaven kommt das Programm nie zur nächsten Anweisung hinter der inneren Schleife, und somit ist eine Konsolenausgabe nicht erreichbar.

Setzen wir statt break heaven ein break hell in die innere Schleife, ändert sich dies:

heaven:

while ( true ) {

hell:

while ( true )

break /* continue */ hell;

System.out.println( "hell" );

}

// System.out.println( "heaven" );

In diesem Szenario ist die Ausgabe »heaven« nicht erreichbar und muss auskommentiert werden. Das break hell in der inneren Schleife wirkt wie ein einfaches break ohne Marke, und das ablaufende Programm führt laufend zu Bildschirmausgaben von »hell«.

[»]  Hinweis

Marken können vor allen Anweisungen (und Blöcke sind damit eingeschlossen) definiert werden; in unserem ersten Fall haben wir die Marke vor die while(true)-Schleife gesetzt. Interessanterweise kann ein break mit einer Marke nicht nur eine Schleife und case verlassen, sondern auch einen ganz einfachen Block:

label:

{



break label;



}

Somit entspricht das break label einem goto zum Ende des Blocks.

Das break kann nicht durch continue ausgetauscht werden, da continue in jedem Fall eine Schleife braucht. Und ein normales break ohne Marke wäre im Übrigen nicht gültig und könnte nicht den Block verlassen.

[»]  Rätsel

Warum übersetzt der Compiler Folgendes ohne Murren?

Listing 2.33    src/main/java/WithoutComplain.java

class WithoutComplain {

static void main( String[] args ) {

http://www.tutego.de/

System.out.print( "Da gibt's Java-Tipps und -Tricks." );

}

}

Mit dem break und einer Marke aus dem switch aussteigen

Da dem break mehrere Funktionen in der Sprache Java zukommen, kommt es zu einer Mehrdeutigkeit, wenn im case-Block einer switch-Anweisung ein break eingesetzt wird.

Im folgenden Beispiel läuft eine Schleife einen String ab. Den Zugriff auf ein Zeichen im String realisiert die String-Objektmethode charAt(int); die Länge eines Strings liefert length(). Als Zeichen im String sollen C, G, A, T erlaubt sein. Für eine Statistik über die Anzahl der einzelnen Buchstaben zählt eine switch-Anweisung beim Treffer jeweils die richtige Variable c, g, a, t um 1 hoch. Falls ein falsches Zeichen im String vorkommt, wird die Schleife beendet. Und genau hier bekommt die Markierung ihren Auftritt:

Listing 2.34    src/main/java/SwitchBreak.java

public class SwitchBreak {



public static void main( String[] args ) {

String dnaBases = "CGCAGTTCTTCGGXAC";

int a = 0, g = 0, c = 0, t = 0;



loop:

for ( int i = 0; i < dnaBases.length(); i++ ) {

switch ( dnaBases.charAt( i ) ) {

case 'A': case 'a':

a++;

break;

case 'G': case 'g':

g++;

break;

case 'C': case 'c':

c++;

break;

case 'T': case 't':

t++;

break;

default:

System.err.println( "Unbekannte Nukleinbasen " + dnaBases.charAt( i ) );

break loop;

}

}

System.out.printf( "Anzahl: A=%d, G=%d, C=%d, T=%d%n", a, g, c, t );

}

}
[»]  Rätsel

Wenn Folgendes in der main(…)-Methode stünde, würde es der Compiler übersetzen? Was wäre die Ausgabe? Achte genau auf die Leerzeichen!

int val = 2;

switch ( val ) {

case 1:

System.out.println( 1 );

case2:

System.out.println( 2 );

default:

System.out.println( 3 );

}

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.

Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern