Kurzeinstieg Java: Kontrollstrukturen

Wie funktioniert die bedingte Verzweigung?

Bearbeiten

Verzweigungen ändern den linearen Programmfluss. In Abhängigkeit von einer Bedingung wird ein Programmteil ausgeführt – oder auch nicht. Verzweigungen werden mit if(bedingung) {etwas code} erzeugt. Wenn sich die Bedingung zu true auswerten lässt, dann wird der Programmcode im Block ausgeführt:

int zahl = 2;
if( zahl % 2 == 0 ) {  // wenn die Zahl gerade ist ...
  System.out.println("2 ist eine gerade Zahl"); // dann führe dieses aus, sonst nicht!
} // fertig

Im Fall, dass etwas zusätzlich ausgeführt werden soll, wenn die Bedingung nicht erfüllt ist, dann schreibt man das mit else:

int zahl = 2;
if( zahl % 2 == 0 ) {  // wenn die Zahl gerade ist ...
  System.out.println(zahl + " ist eine gerade Zahl"); // dann führe dieses aus
} 
else {
  System.out.println(zahl + " ist ungerade.");  // sonst führe dieses aus.
} // fertig

Die geschweiften Klammern kann man bei einzelnen Anweisungen übrigens weglassen.

 boolean beenden = false;
 //[...]
 if (beenden) 
   System.out.println ("Oh ich soll mich beenden");
   System.exit(0);

Das vorstehende Beispiel zeigt einen typischen Fehler in Zusammenhang mit dem if-Konstrukt. Der Befehl System.exit(0); wird immer ausgeführt, da kein Block gebildet wurde.

Es ist auch eine Frage des guten Programmierstils, solche Verzweigungen immer in geschweifte Klammern zu fassen. Dadurch lassen sich die einzelnen Ausdrücke besser dem jeweiligen if zuordnen.

Was macht der Bedingungsoperator?

Bearbeiten

Für manche Anwendungsfälle eignet sich der Bedingungsoperator ?:. Sein Aufbau wird durch folgendes Programm verdeutlicht:

public class Testklasse  {
  public static void main(String[] args) {
    int zahl = 4;
    String geradeText = (zahl % 2 == 0) ? " gerade" : "ungerade";
    System.out.println("Die Zahl " + zahl + " ist " + geradeText); 
  }
}

Dieser Operator wird zumeist in Ausdrücken benutzt, bei denen etwas zugewiesen wird. Wenn also eine Bedingung erfüllt ist, dann gib den ersten Teil als Ausdruck zurück, sonst den Teil, der hinter dem : steht. Bedingungsoperatoren verwendet man überall dort, wo eine if-else-Konstruktion zu lang wäre.

Wie kann man lesbar vielfach verzweigen?

Bearbeiten

if-else-Konstrukte kann man beliebig verschachteln. Bei einer Auswahl über sehr viele Kategorien werden solche Konstrukte schnell unübersichtlich. Hier kann switch helfen. Diese Art der Mehrfachverzweigung können Sie nur mit ganzzahligen numerischen Typen (int, byte, short), Zeichen (char) und String verwenden.

 
public class Testklasse {
  public static void main(String[] args) {
      String wochentag = "Montag";
      switch( wochentag ) {
        case "Montag":
        case "Dienstag":
        case "Mittwoch":
          System.out.println("Die Woche nimmt kein Ende...");
          break;            // Bei Montag, Dienstag oder Mittwoch ist hier Schluss
        case "Donnerstag":
          System.out.println("Morgen ist schon Freitag");
          break;            // Nur bei "Donnerstag" ist hier Schluss
        case "Freitag":
          System.out.println("Endlich Freitag");
          break;
        default:            // alle anderen Fälle
          System.out.println("Endlich Wochenende!!!!!");
          break;
      } // Ende von switch()
  }
}

Mit switch wird die Auswahl getroffen, mit case werden die einzelnen Fälle selektiert. Hat man Fälle, die man nicht im einzelnen explizit abfangen möchte, dann folgt default.

Der Wert hinter case muss eine Konstante sein und dient dem Vergleich mit dem übergebenen Wert (hier Montag). Wenn diese gleich sind werden alle Anweisungen bis zum nächsten break oder dem Ende des Blocks ausgeführt. Wurden keine Übereinstimmung gefunden so werden die Anweisung nach default ausgeführt. Java in Versionen vor 7 erlaubten keine Variablen vom Typ String.

For-each-Schleife

Bearbeiten

Besonders im Zusammenhang mit Feldern ist eine for-Schleife gebräuchlich, die über jedes Element des Arrays iteriert. "Für jedes Element eines Arrays mache folgendes...":

public class T  {
  public static void main(String[] args) {
      int[] zahlen = {1, 2, 3, 4, 5, 6};        // Array

      for(int zahl : zahlen) {     // für jede Zahl im Array...
        System.out.println(zahl + " ist eine sehr schöne Zahl.");    // gib sie aus
      }
  }
}

Die Variable zahl ist nur innerhalb der Schleife bekannt. Der Datentyp muss zum Datentyp der Feld-Elemente passen. Neben Arrays sind allgemeine Collection-Klassen (wir kommen später darauf zu sprechen) die Kernanwendungen dieser Schleife, die Oracle als "enhanced for statement" bezeichnet.

Allgemeine For-Schleife

Bearbeiten

Die for-Schleife oder Zählschleife zählt von einem vorgegebenen Startwert zu einem ebenfalls vorgegebenen Endwert und führt für jeden Schritt von Start- bis Endwert alle Anweisungen im Schleifenkörper aus. Es muss also zusätzlich festgelegt werden, in welchen Intervallen bzw. Schritten von Start bis Ende gezählt wird.

For-Schleifen bestehen aus folgenden Teilen bzw. Anweisungen und Bedingungen:

  • Schleifenkopf
    • Initialisierung der Zählervariable auf den Startwert
    • Bedingung mit Limitierung auf den Endwert. Ist diese Bedingung true dann wird die Zählschleife weiterhin ausgeführt
    • Schrittweite
  • Schleifenkörper (Schleifenrumpf)
    • Anweisungen, die pro Schleifendurchlauf ausgeführt werden sollen

Die Syntax

Bearbeiten

Die grundlegende Syntax sieht folgendermaßen aus:

for( Initialisierung Zahlervariable; Limitierung; Zählen ){
	...
	Schleifenkörper mit Anweisungen
	...
}

Beispiele:

for(int zahl = 1; zahl < 10; zahl++) {  // 
        System.out.println(zahl);
      }

In obigem Beispiel sieht man eine for-Schleife, die von 1 bis 9 zählt. Warum nur bis 9? Weil die Limitierung kleiner als 10 und nicht kleiner gleich 10 lautet.

  • Zunächst muss eine Zählervariable auf einen Startwert initialisiert werden int zahl=1. Wird diese Variable im Schleifenkopf deklariert, ist sie ausschließlich im Schleifenkopf und im Schleifenkörper bekannt. Außerhalb der Schleife kann die Zählervariable nicht angesprochen werden.
  • Dann wird die Limitierung for-Schleife festgelegt: i < 10. Sie läuft so lange wie zahl kleiner als 10 ist. Trifft diese Bedingung nicht zu, werden die Anweisungen im Schleifenkörper nicht ausgeführt. Die Limitierung muss also immer einen boolschen Wert (true oder false) ergeben.
  • Zuletzt muss noch definiert werden in welchen Intervallen gezählt wird: zahl++. Die Schleife zählt also in Schritten von 1 nach oben bzw. addiert nach jedem Schleifendurchlauf 1 auf die Zählervariable zahl.

Ablauf einer for-Schleife

Bearbeiten

Nehmen wir folgende Schleife an:

for(int i = 0; i < 3; i++) {
	System.out.println( i );
}

Sie wird folgendermaßen durchlaufen:

  1. Start: Initialisierung der Zählervariable auf den Wert 0
  2. Prüfung der Limitierung: i = 0 ist kleiner als 3 ( i < 3 == true ), also dürfen die Anweisungen des Schleifenkörpers ausgeführt werden
  3. Erster Durchlauf: Die Zählervariable i wird am Bildschirm ausgegeben: 0
  4. Hochzählen: Die Zählervariable wird um 1 erhöht: 0 + 1 = 1
  5. Prüfung der Limitierung: i = 1 ist kleiner als 3 ( i < 3 == true ), also dürfen die Anweisungen des Schleifenkörpers ausgeführt werden
  6. Zweiter Durchlauf: Die Zählervariable i wird am Bildschirm ausgegeben: 1
  7. Hochzählen: Die Zählervariable wird um 1 erhöht: 1 + 1 = 2
  8. Prüfung der Limitierung: i = 2 ist kleiner als 3 ( i < 3 == true ), also dürfen die Anweisungen des Schleifenkörpers ausgeführt werden
  9. Dritter Durchlauf: Die Zählervariable i wird am Bildschirm ausgegeben: 2
  10. Hochzählen: Die Zählervariable wird um 1 erhöht: 2 + 1 = 3
  11. Prüfung der Limitierung: i = 3 genauso groß wie 3 ( i < 3 == false ), also dürfen die Anweisungen des Schleifenkörpers nicht mehr ausgeführt werden

Verwendungszweck

Bearbeiten

For-Schleifen sind nützlich, um eine bekannte Anzahl an wiederkehrenden Anweisungen auszuführen. Sei es das Füllen eines Arrays, das Erzeugen einer gewissen Anzahl an Objekten oder die Ausgabe der ersten Zeilen einer Datenbankabfrage.

public class Testklasse  {

  public static void main(String[] args) {

      // erzeugt ein Array mit zehn Integer-Werten
      int[] einArray = new int[10];

      // nun wird das Befüllen des Arrays ebenfalls durch eine Schleife realisiert
      // es werden nur ungerade Zahlen ins Array gesteckt
      for(int i = 0; i < einArray.length; i++) {
        einArray[i] = 2 * i + 1;
      }

      // For-Schleife, die die im Array enthaltenen Werte ausgibt
      for( int zahl : einArray ){
          System.out.println( zahl );
      }
  }
}

Mittels .length erhält man die Länge des Arrays als ganze Zahl. Somit lässt sich immer die Länge des Arrays bestimmen, das ist praktisch, man ist so weniger auf Konstanten angewiesen.

Ein Array kann also mittels einer for-Schleife durchlaufen werden, da man einen Startwert = 0 sowie einen Endwert = .length - 1 hat. Da man alle Werte aus dem Array auslesen möchte (und nicht nur bestimmte), wird die Zählervariable immer um eins erhöht.

Im Schleifenkörper kann nun jeder Arrayplatz mit der Zählervariable angesprochen werden: einArray[i].

Es ist natürlich ebenfalls möglich mehrmals Objekte des gleichen Typs zu erstellen.

// ein Array zur Aufnahme von Objekten erzeugen
Integer[] einArray = new Integer[100];  // Platz für 100 Objekte vom Typ Integer

// Befüllen des Arrays mit 100 Objekten vom Typ Integer
for( int i = 0; i < einArray.length; i++ ) {
	einArray[i] = new Integer( i );
}

Variationen

Bearbeiten

Bei der Implementierung des Schleifenkopfes ist man nicht strikt an die in obigem Beispiel vorgestellte Form gebunden. Vieles ist möglich, solange man sich an die Syntax hält. Die folgenden Beispiele sollen nur kleine Anregungen sein.

// Bsp.: Initialisierung der Zählvariable mit einer negativen Zahl
for( byte i = -120; i < 100; i++ ){
	System.out.println( i );
}	

int i = 50; // Bsp.: Variable für den Startwert außerhalb der Schleife festlegen
for(  ; i < 100; i++ ){
	System.out.println( i );
}

// Bsp.: abwärts zählen
for( int i = 150; i >= 100; i-- ){
	System.out.println( i );
}

// Bsp.: größeres Intervall
for( int i = 1; i <= 100; i+=10 ){
	System.out.println( i );
}

// Ausgabe aller 2er-Potenzen kleiner als 100
for( int i = 1; i <= 100; i*=2 ){
	System.out.println( i );
}

// Endlosschleife
for( ; ; ){
	System.out.println( "Hallo, Welt!" );
}

Schleife mit Vorabprüfung (while)

Bearbeiten

Die while-Schleife führt den Anweisungsblock aus, sofern die Bedingung im Schleifenkopf true ergibt. Die Prüfung der Bedingung erfolgt dabei vor dem Betreten der Schleife. Beispiele

 while (true) {} // Endlosschleife

 int i = 0;
 while (i < 10) {  // Ausführungsanzahl 10
    i++;
 }

 int i = 0;
 while (i < 0) {  // wird nicht ausgeführt.
    System.out.println ("Schleifentest");
  }

Kopfgesteuerte Schleifen werden benutzt, wenn man sich nicht sicher ist, ob eine Schleife überhaupt ausgeführt werden muss. Sie wird also "mindestens 0-mal" ausgeführt.

Einige Sortieraufgaben erfordern beispielsweise genau so ein Vorgehen: "Solange meine Sachen nicht sortiert sind, sortiere". Wenn die Sachen schon sortiert sind, braucht nichts sortiert und der Schleifenkörper braucht folglich nicht besucht zu werden.

Schleife mit Nachprüfung (do)

Bearbeiten

Rumpfgesteuerte Schleifen sind quasi das Gegenstück zu kopfgesteuerten Schleifen. Die do-Schleife führt alle beinhalteten Anweisungen solange aus, wie die Prüfung true ergibt. Die Prüfung der Bedingung erfolgt dabei nach dem ersten Schleifendurchlauf. Zu einer do Schleife wird stets das Schlüsselwort while zur Bedingungsangabe benötigt. Beispiele:

 do {} while (true); // Endlosschleife

 int i = 0;
 do {
    i++;
 } while (i < 10); // Ausführungsanzahl 10

 int i = 0;
 do { 
    System.out.println ("Hallo, Welt!");
 } while (i < 0);

Die do-Schleife wird mindestens 1-mal ausgeführt.

Hier wird die do-Schleife benutzt, um mit Bubblesort ein Array aufsteigend zu sortieren:

public class Testklasse  {

  public static void main(String[] args) {

    // unsortiertes Array
    int[] einArray = {10, 9, 8, 7, 6, 5, 4, 3, 2 };
    int hilfsvariable; 
    boolean vertauscht;

    do {
      vertauscht = false;
      for(int index = 0; index < einArray.length - 1; index++) {
        if(einArray[index] > einArray[index + 1]) { // wenn falsche Reihenfolge

          // vertausche 2 Nachbarn im Array
          hilfsvariable = einArray[index];
          einArray[index] = einArray[index + 1];
          einArray[index + 1] = hilfsvariable;

          // es wurde vertauscht
          vertauscht = true;
        } // ende von if
      } // ende von for
    } while (vertauscht); // weitermachen, so lange das Array unsortiert ist

    // Array ausgeben
    for(int elem : einArray) {
        System.out.print( elem + " " );
    }
    System.out.println();
  }
}

Schleifen verlassen und überspringen (break und continue)

Bearbeiten

Innerhalb einer do, while oder for-Schleife kann mit break die gesamte Schleife verlassen werden. Soll nicht die gesamte Schleife, sondern nur der aktuelle Schleifendurchlauf verlassen und mit dem nächsten Durchlauf begonnen werden, kann die Anweisung continue verwendet werden. Im folgenden Beispiel werden in einem Iterator über eine Personenkartei alle Personen gesucht, die zu einer Firma gehören. Dabei werden gesperrte Karteikarten übersprungen. Wird eine Person gefunden, die zu dieser Firma gehört und als Freiberufler tätig ist, wird die weitere Suche abgebrochen, da angenommen wird, dass ein Freiberufler der einzige Angehörige seiner Firma ist.

  String desiredCompany = "Wikimedia";
  Person[] persons;
  StringBuffer nameListOfWikimedia;
  
  for (int i=0;i<MAX_PERSONEN;i++) {
    if (persons[i].isLocked()) {
      continue; // Gesperrte Person einfach überspringen
    } 
    String company = persons[i].getCompany();
    if (company.equals(desiredCompany)) {
      nameListOfWikimedia.append(persons[i].getName());
      if (persons[i].isFreelancer()) {
        break; // Annahme: Ein Freelancer hat keine weiteren Mitarbeiter
      }
    }
  }

Diese Aussprünge ähneln den goto-Befehlen anderer Programmiersprachen, sind aber nur innerhalb der eigenen Schleifen erlaubt. Bei Schachtelung mehrerer Schleifen können diese auch mit Bezeichnern (Labels) versehen werden, so dass sich die break- oder continue-Anweisung genau auf eine Schleife beziehen kann:

  catalog: while(catalogList.hasNext()) {
    product: while (productList.hasNext()) {
      price: while (priceList.hasNext()) {
        [...]
        if (priceTooHigh) {
          continue product;
        }
      }
    }
  }

Übungen

Bearbeiten
  1. Schreibe ein einfaches Programm, welches für jeden Monat die Anzahl der Tage ausgibt. Nutze hierzu die einfache Verzweigung.
  2. Schreibe ein einfaches Programm, welches für jeden Monat die Anzahl der Tage ausgibt. Nutze hierzu die Mehrfachverzweigung.
  3. Schreibe eine einfache Applikation, welche mit Hilfe der Zählschleife die Übergabeparameter ausgibt. Sofern du das JDK 1.5 oder höher verwendest nutze hierfür die alte und neue Variante.
  4. Wie oft wird die folgende do-Schleife ausgeführt und warum?
int i = 10;
do {
  i -= 3;
}
while (i > 5);