Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Java ist auch eine Insel von Christian Ullenboom
Buch: Java ist auch eine Insel (Galileo Computing)
gp Kapitel 2 Sprachbeschreibung
gp 2.1 Anweisungen und Programme
gp 2.2 Elemente der Programmiersprache Java
gp 2.2.1 Textkodierung durch Unicode-Zeichen
gp 2.2.2 Unicode-Tabellen unter Windows
gp 2.2.3 Literale
gp 2.2.4 Bezeichner
gp 2.2.5 Reservierte Schlüsselwörter
gp 2.2.6 Token
gp 2.2.7 Semantik
gp 2.2.8 Kommentare
gp 2.2.9 Funktionsaufrufe als Anweisungen
gp 2.2.10 Die leere Anweisung
gp 2.2.11 Der Block
gp 2.3 Datentypen
gp 2.3.1 Primitive Datentypen
gp 2.3.2 Wahrheitswerte
gp 2.3.3 Variablendeklarationen
gp 2.3.4 Ganzzahlige Datentypen
gp 2.3.5 Die Fließkommazahlen
gp 2.3.6 Alphanumerische Zeichen
gp 2.3.7 Die Typanpassung (das Casting)
gp 2.3.8 Lokale Variablen, Blöcke und Sichtbarkeit
gp 2.3.9 Initialisierung von lokalen Variablen
gp 2.4 Ausdrücke, Operanden und Operatoren
gp 2.4.1 Zuweisungsoperator und Verbundoperator
gp 2.4.2 Präfix- oder Postfix-Inkrement und -Dekrement
gp 2.4.3 Unäres Minus und Plus
gp 2.4.4 Arithmetische Operatoren
gp 2.4.5 Die relationalen Operatoren
gp 2.4.6 Logische Operatoren
gp 2.4.7 Reihenfolge und Rang der Operatoren in der Auswertungsreihenfolge
gp 2.4.8 Überladenes Plus für Strings
gp 2.4.9 Was C(++)-Programmierer vermissen könnten
gp 2.5 Bedingte Anweisungen oder Fallunterscheidungen
gp 2.5.1 Die if-Anweisung
gp 2.5.2 Die Alternative wählen mit einer if/else-Anweisung
gp 2.5.3 Die switch-Anweisung bietet die Alternative
gp 2.6 Schleifen
gp 2.6.1 Die while-Schleife
gp 2.6.2 Schleifenbedingungen und Vergleiche mit ==
gp 2.6.3 Die do/while-Schleife
gp 2.6.4 Die for-Schleife
gp 2.6.5 Ausbruch planen mit break und Wiedereinstieg mit continue
gp 2.6.6 break und continue mit Sprungmarken
gp 2.7 Methoden einer Klasse
gp 2.7.1 Bestandteil einer Funktion
gp 2.7.2 Aufruf
gp 2.7.3 Methoden ohne Parameter
gp 2.7.4 Statische Methoden (Klassenmethoden)
gp 2.7.5 Parameter und Wertübergabe
gp 2.7.6 Methoden vorzeitig mit return beenden
gp 2.7.7 Nicht erreichbarer Quellcode bei Funktionen
gp 2.7.8 Rückgabewerte
gp 2.7.9 Methoden überladen
gp 2.7.10 Vorinitialisierte Parameter bei Funktionen
gp 2.7.11 Finale lokale Variablen
gp 2.7.12 Finale Referenzen in Objekten und das fehlende const
gp 2.7.13 Rekursive Funktionen
gp 2.7.14 Die Ackermann-Funktion
gp 2.7.15 Die Türme von Hanoi
gp 2.8 Weitere Operatoren
gp 2.8.1 Bitoperationen
gp 2.8.2 Vorzeichenlose Bytes in ein Integer und Char konvertieren
gp 2.8.3 Variablen mit Xor vertauschen
gp 2.8.4 Die Verschiebeoperatoren
gp 2.8.5 Setzen, Löschen, Umdrehen und Testen von Bits
gp 2.8.6 Der Bedingungsoperator
gp 2.9 Einfache Benutzereingaben


Galileo Computing

2.5 Bedingte Anweisungen oder Fallunterscheidungendowntop

Kontrollstrukturen dienen in einer Programmiersprache dazu, Programmteile unter bestimmten Bedingungen auszuführen. Java bietet zum Ausführen verschiedener Programmteile eine if- und if/else-Anweisung sowie die switch-Anweisung. Neben der Verzweigung dienen Schleifen dazu, Programmteile mehrmals auszuführen.

Obwohl ein Sprung mit goto nicht möglich ist, besitzt Java eine spezielle Sprungvariante: continue und break mit definierten Sprungzielen.


Galileo Computing

2.5.1 Die if-Anweisungdowntop

Die if-Anweisung besteht aus dem Schlüsselwort if, dem zwingend ein Ausdruck mit dem Typ boolean in Klammern folgt. Es folgt eine Anweisung, die oft eine Blockanweisung ist.

if ( Ausdruck )
  Anweisung

Die Abarbeitung der Anweisung hängt nun vom Ausdruck ab. Ist das Ergebnis des Ausdrucks wahr (true), so wird die Anweisung ausgeführt. Ist das Ergebnis des Ausdrucks falsch (false), so wird mit der ersten Anweisung nach der if-Anweisung fortgefahren.


Beispiel Ein Relationenvergleich
if ( x < y )
  System.out.println( "x ist kleiner als y" );

Im Gegensatz zu C(++) muss der Testausdruck in der if-Anweisung (übrigens auch in den folgenden Schleifen) vom Typ boolean sein. In C(++) wird ein numerischer Ausdruck als wahr bewertet, wenn das Ergebnis des Ausdrucks ungleich 0 ist.

Betrachten wir in einer if-Anweisung den Vergleich, ob ein Objekt existiert. Dann ist dies mit null zu vergleichen.1 Die Referenz auf das Objekt steht in der Variablen ref.

if ( ref != null )
  ...

if-Anfragen und Blöcke

Hinter dem if und der Bedingung erwartet der Compiler eine Anweisung. Wenn wir jedoch mehrere Anweisungen in Abhängigkeit von der Bedingung ausführen wollen, so müssen wir einen Block verwenden, denn andernfalls ordnet der Compiler nur die nächstfolgende Anweisung der Fallunterscheidung zu, auch wenn mehrere Anweisungen optisch abgesetzt sind.2 Dies ist eine große Gefahr für Programmierer, die optisch Zusammenhänge schaffen wollen, die in Wirklichkeit nicht existieren.


Beispiel Eine if-Anweisung soll testen, ob die Variable y den Wert 0 hat. In dem Fall soll sie die Variable x auf 0 setzen und zusätzlich auf dem Bildschirm »Null« anzeigen. Zunächst die semantisch falsche Variante:
if ( y == 0 )
  x = 0;
  System.out.println( "Null" );

Sie ist semantisch falsch, da unabhängig von y immer eine Ausgabe erscheint. Der Compiler interpretiert die Zeilen in folgendem Zusammenhang:

if ( y == 0 )
  x = 0;
System.out.println( "Null" );

Einrückungen ändern nicht die Semantik des Programms! Einschübe können das Verständnis nur empfindlich stören. Damit das Programm korrekt wird, müssen wir einen Block verwenden und die Anweisungen zusammensetzen.


Beispiel Ein korrekt geklammerter Ausdruck:
if ( y == 0 ) {
  x = 0;
  System.out.println( "Null" );
}

Zusammengesetzte Bedingungen

Unsere bisherigen Abfragen waren sehr einfach, jedoch kommen in der Praxis viel komplexere Bedingungen vor. Dafür werden häufig die logischen Operatoren &&, || beziehungsweise ! verwendet. Wenn wir etwa testen wollen, ob eine Zahl x entweder gleich 7 oder größer gleich 10 ist, schreiben wir die zusammengesetzte Bedingung

if ( x == 7 || x >= 10 )
  ...

Sind die logisch verknüpften Ausdrücke komplexer, so sollten zur Unterstützung der Lesbarkeit die einzelnen Bedingungen in Klammern gesetzt werden, da nicht jeder sofort die Tabelle mit den Vorrangregeln für die Operatoren im Kopf hat.

Abbildung
Hier klicken, um das Bild zu Vergrößern

if und (Strg)+(____) bietet an, eine if-Anweisung mit Block anzulegen.

Abbildung
Hier klicken, um das Bild zu Vergrößern


Galileo Computing

2.5.2 Die Alternative wählen mit einer if/else-Anweisungdowntop

Neben der einseitigen Alternative existiert die zweiseitige Alternative. Das optionale Schlüsselwort else veranlasst die Ausführung der alternativen Anweisung, wenn der Test falsch ist:

if ( Ausdruck )
  Anweisung1
else
  Anweisung2

Falls der Ausdruck wahr ist, wird die Anweisung 1 ausgeführt, andernfalls Anweisung 2. Somit ist sichergestellt, dass in jedem Fall eine Anweisung ausgeführt wird.

if ( x < y )
  System.out.println( "x ist echt kleiner als y." );
else
  System.out.println( "x ist größer oder gleich y." );

Dangling-Else-Problem

Bei Verzweigungen mit else gibt es ein bekanntes Problem, welches Dangling-Else-Problem genannt wird. Zu welcher Anweisung gehört das folgende else?

if ( Ausdruck1 )
  if ( Ausdruck2 )
    Anweisung1;
else
  Anweisung2;

Die Einrückung suggeriert, dass das else die Alternative zur ersten if-Anweisung ist. Dies ist aber nicht richtig. Die Semantik von Java (und auch fast aller anderen Programmiersprachen) ist so definiert, dass das else zum innersten if gehört. Daher lässt sich nur der Programmiertipp geben, die if-Anweisungen zu klammern:

if ( Ausdruck1 )
{
  if ( Ausdruck2 )
  {
    Anweisung1;
  }
}
else
{
  Anweisung2;
}

So kann eine Verwechslung gar nicht erst aufkommen.


Beispiel Wenn das else immer zum innersten if gehört, und das ist nicht erwünscht, können wir, wie gerade gezeigt, mit geschweiften Klammern arbeiten oder auch eine leere Anweisung im else-Zweig hinzufügen:
if ( x >= 0 )
  if ( x != 0 )
    System.out.println( "x echt größer Null" );
  else
    ; // x ist gleich Null
else
  System.out.println( "x echt kleiner Null" );

Das böse Semikolon

An dieser Stelle ist ein Hinweis angebracht. Ein Programmieranfänger schreibt gerne hinter die schließende Klammer der if-Anweisung ein Semikolon. Das führt zu einer ganz anderen Ausführungsfolge. Ein Beispiel:

int alter = 29;
if ( alter < 0 ) ;
  System.out.println( "Aha, noch im Mutterleib" );
if ( alter > 150 ) ;
  System.out.println( "Aha, ein neuer Moses" );

Das Semikolon führt dazu, dass die leere Anweisung in Abhängigkeit von der Bedienung ausgeführt wird und unabhängig vom Inhalt der Variable alter immer die Ausgabe »Aha, noch im Mutterleib« erzeugt. Das ist sicherlich nicht beabsichtigt.

Folgen hinter einer if-Anweisung zwei Anweisungen, die nicht durch eine Blockanweisung zusammengefasst sind, dann wird die eine folgende else-Anweisung als Fehler bemängelt, da der zugehörige if-Zweig fehlt. Der Grund ist, dass der if-Zweig nach der ersten Anweisung ohne else zu Ende ist.

int alter = 29;
if ( alter < 0 ) ;
  System.out.println( "Aha, noch im Mutterleib" );
else ( alter > 150 ) ;
  System.out.println( "Aha, ein neuer Moses" );

Das führt zu der Fehlermeldung 'else' without 'if'.

Mehrfachverzweigung beziehungsweise geschachtelte Alternativen

if-Anweisungen zur Programmführung kommen sehr häufig in Programmen vor, und noch häufiger ist es, eine Variable auf einen bestimmten Wert zu prüfen. Dazu werden if- und if/else-Anweisung gerne geschachtelt. Wenn eine Variable einem Wert entspricht, dann wird eine Anweisung ausgeführt, sonst wird die Variable mit einem anderen Wert getestet und so weiter.

Dieser Ansatz ist sehr umständlich und kostet zudem noch Rechenzeit, da in jedem Fall drei Bedingungen geprüft werden. Wenn also x größer null ist, werden dennoch zwei Vergleiche gemacht. Wir schachteln daher in einer kleinen Programmverbesserung die Alternativen und arbeiten dann mit einer Abfolge von sequenziell abhängigen Alternativen:3

if ( x > 0 )
  signum = 1;
else
  if ( x < 0 )
    signum = -1;
  else
    signum = 0;

Jetzt werden nur noch so viele Bedingungen geprüft wie zur Entscheidung notwendig sind. Die eingerückten Verzweigungen nennen sich auch angehäufte if-Anweisungen oder if-Kaskade, da jede else-Anweisung ihrerseits weitere if-Anweisungen enthält, bis alle Abfragen gemacht sind.


Beispiel Kaskadierte if-Anweisungen:
if ( monat == 4 )
  tage = 30;
else
  if ( monat == 6 )
    tage = 30;
  else
    if ( monat == 9 )
      tage = 30;
    else
      if ( monat == 11 )
        tage = 30;
      else
        if ( monat == 2 )
          if ( schaltjahr )
            tage = 29;
          else
            tage = 28;
        else
          tage = 31;


Galileo Computing

2.5.3 Die switch-Anweisung bietet die Alternativetoptop

In Java4 gibt es eine Kurzform für speziell gebaute, angehäufte if-Anweisungen - die Anweisung mit switch und case:

switch ( Ausdruck )
{
  case Konstante:
    Anweisungen
}

Die switch-Anweisung ist eine einfache Form der Mehrfachverzweigung. Sie vergleicht nacheinander den Ausdruck hinter dem switch (ein primitiver Typ wie byte, char, short oder int) mit jedem einzelnen Fallwert. Alle Fallwerte müssen unterschiedlich sein. Stimmt der Ausdruck mit der Konstanten überein, so wird die Anweisung beziehungsweise die Anweisungen hinter der Sprungmarke ausgeführt.


Hinweis Eine Einschränkung der switch-Anweisung besteht darin, dass die Tests und die Konstanten nur auf den primitiven Datentyp int beschränkt sind.

Es können also keine größeren Typen wie long oder Fließkommazahlen wie float beziehungsweise double oder gar Objekte benutzt werden. Als Alternative bleiben nur angehäufte if-Anweisungen. Dies ist auch der einzige Weg, um Bereiche abzudecken.

Alles andere abdecken mit default

Gibt es keine Übereinstimmung mit einer Konstanten, so lässt sich optional die Sprungmarke default einsetzen:

switch ( ausdruck )
{
  case Konstante:
    Anweisungen
  ...
  default:
}

Ohne Übereinstimmung mit einem konkreten Ziel geht die Abarbeitung des Programmcodes hinter default weiter. default kann auch zwischen den Konstanten eingesetzt werden.


Beispiel Ein Taschenrechner mit Alternative
switch ( op )
{
  case '+':   // addiere
    break;
  case '-':   // subtrahiere
    break;
  case '*':   // multipliziere
    break;
  case '/':   // dividiere
    break;
  default:
    System.err.println( "Operand nicht definiert!" );
}

switch hat Durchfall

Bisher haben wir in die letzte Zeile eine break-Anweisung gesetzt. Ohne ein break würden nach einer Übereinstimmung alle nachfolgenden Anweisungen ausgeführt. Sie laufen somit in einen neuen Abschnitt herein bis ein break oder das Ende von switch erreicht ist. Da dies vergleichbar mit einem Spielzeug ist, bei dem Kugeln von oben nach unten durchfallen, nennt sich dieses auch Fall-Through. Ein häufiger Programmierfehler ist, das break zu vergessen, und daher sollte ein beabsichtigter Fall-Through immer als Kommentar angegeben werden.


Beispiel Über dieses Durchfallen ist es möglich, bei unterschiedlichen Werten immer die gleiche Anweisung ausführen zu lassen:
switch ( buchstabe )
{
  case 'a':                    // Durchfallen
  case 'e':
  case 'i': case 'o': case 'u':
    vokal = true;
    break;
  default:
    vokal = false;
}

In dem Beispiel bestimmt eine case-Anweisung, ob die Variable buchstabe ein Vokal ist. Fünf case-Anweisungen decken jeweils einen Buchstaben ab. Stimmt die Variable mit einer Konstanten überein, so »fällt« der Interpreter in den Programmcode der Zuweisung. Dieses Durchfallen über die case-Zweige ist praktisch, so wie es unser Programmcode für das Ist-Vokal-Problem zeigt. Der erste case-Zweig setzt die boolesche Variable vokal bei einem Vokal auf wahr. Tritt die Bedingung nicht ein, so weist die Anweisung im default-Teil der Variablen vokal den Wert falsch zu.


Hinweis Obwohl ein fehlendes break zu lästigen Programmierfehlern führt, haben die Java-Entwickler dieses Verhalten vom syntaktischen Vorgänger C übernommen. Eine interessante Lösung wäre gewesen, das Verhalten genau umzudrehen und das Durchfallen explizit einzufordern, zum Beispiel mit einem Schlüsselwort.

switch und (Strg)+(_____) bietet an, ein Grundgerüst für eine switch Fallunterscheidung anzulegen.

Abbildung
Hier klicken, um das Bild zu Vergrößern






1 Und nicht einfach if (ref) wie in C(++).

2 In der Programmiersprache Python bestimmt die Einrückung die Zugehörigkeit.

3 In der Programmiersprache Euphoria (die Webseite>http://www.rapideuphoria.com/ wirbt mit safe und sexy - Huuh) heißt das einfach: return (x > 0) - (x < 0).

4 Und auch in C(++).





Copyright (c) Galileo Press GmbH 2004
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.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de