Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5 (Tiger-Release)
Buch: Java ist auch eine Insel
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
    gp 2.4.2 Verbundoperatoren
    gp 2.4.3 Präfix- oder Postfix-Inkrement und -Dekrement
    gp 2.4.4 Unäres Minus und Plus
    gp 2.4.5 Arithmetische Operatoren
    gp 2.4.6 Die relationalen Operatoren
    gp 2.4.7 Logische Operatoren
    gp 2.4.8 Reihenfolge und Rang der Operatoren in der Auswertungsreihenfolge
    gp 2.4.9 Überladenes Plus für Strings
    gp 2.4.10 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 Funktionen (Klassenmethoden)
    gp 2.7.5 Parameter, Argument 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 Vorgegebener Wert für nicht aufgeführte Argumente
    gp 2.7.11 Finale lokale Variablen
    gp 2.7.12 Rekursive Funktionen
    gp 2.7.13 Die Ackermann-Funktion
    gp 2.7.14 Die Türme von Hanoi
  gp 2.8 Dokumentationskommentare
    gp 2.8.1 Ein Dokumentationskommentar setzen
    gp 2.8.2 Mit javadoc eine Dokumentation erstellen
    gp 2.8.3 Generierte Dateien
    gp 2.8.4 Weitere Dokumentationskommentare
    gp 2.8.5 Schalter für das Programm javadoc
    gp 2.8.6 JavaDoc und Doclets
    gp 2.8.7 Bitoperationen
    gp 2.8.8 Vorzeichenlose Bytes in ein Integer und Char konvertieren
    gp 2.8.9 Variablen mit Xor vertauschen
    gp 2.8.10 Die Verschiebeoperatoren
    gp 2.8.11 Setzen, Löschen, Umdrehen und Testen von Bits
    gp 2.8.12 Der Bedingungsoperator
  gp 2.9 Einfache Benutzereingaben


Galileo Computing

2.5 Bedingte Anweisungen oder Fallunterscheidungedowntop

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. Bedeutend im Wort »Kontrollstrukturen« ist der Teil »Struktur«, denn die Struktur zeigt sich schon durch das bloße Hinsehen. Als es noch keine Schleifen und »hochwertige« Kontrollstrukturen gab, sondern nur ein Wenn/Dann und einen Sprung, war die Logik des Programms nicht offensichtlich; das Resultat nannte sich Spaghetticode. Obwohl ein allgemeiner Sprung in Java mit goto nicht möglich ist, besitzt die Sprache dennoch eine spezielle Sprungvariante. Innerhalb von Schleifen sind continue und break mit definierten Sprungzielen erlaubt.


Galileo Computing

2.5.1 Die if-Anweisundowntop

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. 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. 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-Anweisung  downtop

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:


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 Alternativtoptop

In Java 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 sollte nicht dafür verwendet werden, den letzten gültigen Fall abzudecken. Dazu ist besser ein case gedacht; die Idee von default ist, alles andere abzuhandeln.


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!" );
}

default kann auch zwischen den Konstanten eingesetzt werden, so dass case-Anweisungen vorangehen und nachfolgen. Das ist aber wenig übersichtlich.

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 )
{
  casea:                    // Durchfallen
  casee:
  casei: caseo: caseu:
    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. Es ist übrigens nicht möglich, vor dem nächsten case eine Anweisung wie if zu beginnen, so dass dann der Rumpf von if zu einem der nächsten case-Blöcken gehört.


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. C# wählt hier auch eine andere Lösung.

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

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

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






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 © 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