uio--WebPageMain-Module

JAVA-A1 Intro JAVA Technologie vs. JAVA Sprache Objektorientierung (Prinzip) Basics Variables Primitive Typen Wrapper

Operators Rechenoperatoren instanceOf Operator Operators a=b?c:d;

Control-Flow-Statements for Laufvariable Init Conditions Increment After Beispiele zu for(;;) do-while if-elseif-else else if else Beispiele zur Fallunterscheidung if/else if/else in Kurznotation switch switch(int) switch(String) switch(enum) switch(obj) Dispatch via Maps (Ausblick/Example) Branching-Statements break continue Schleifen mit Labels Erweiterte for-Schleife für Collections Switch Expressions (JEP 361, Java 14) mit yield und Arrow-Notation Branching in Streams @! verschieben zu C2!

Arrays Arrays Array-Literale zur Initalisierung Multi-Dimensional-Array Bracket-Array JaggedArray-Array 3D-Array als Matrix 3D-Jagged-Array Arrays in String-Darstellung ausgeben

Overview

JAVA Control-Flow-Statements | Overview

Mit Hilfe von Control-Flow-Statements oder Kontrollfluss-Ausdrücken ist es möglich, den Programmverlauf einem Programm auch in JAVA steuern zu können. Neben den üblichen Typen für Schleifen und Fallunterscheidungen, wie es diese auch in anderen Sprachen gibt, gibt es für viele dieser Varianten aber für die eigentliche Praxis später Varianten über Collection-Klassen, Stream-API und Funktionale Programmierung.

Das Verständnis der verschiedenen grundlegenden Control-Flow-Mechanismen wie kopf- oder fußgesteuerte Schleife, Prüfung von Bedingungen, Fail-Fast-Pattern sind als Grundlage für Aufbauthemen deshalb unverzichtbar.

for-Schleife

Control-Flow-Statements | for-Schleife

Eine typische for-Schleife in JAVA besteht aus im Kopf der Schleife aus drei Bestandteilen: Einem Block mit der Initialisierung, einem zweiten Block mit Bedingungen und einem dritten Block welcher nach jeder Schleife durchgeführt.

In diesem Beispiel zeigen wir, wie man einen Array mit Hilfe einer for-Schleife durchläuft.

Laufvariable

Bei einer Laufvariable handelt es sich im Kontext von Schleifen in JAVA und anderen Programmiersprachen um eine Variable, welche in jedem Durchlauf der Schleife verändert wird.

Laufvariablen heißen üblicherweise i, j oder k. Man beginnt üblicherweise bei der innersten Schleife mit i und nutzt bei einer Verschachtelung von Schleifen dann die forlaufenden Buchstaben als Bezeichner.

Initialisierungsblock

Der Initialisierungsblock kann int i=0; beinhalten, muss es aber genau genommen nicht. Alle innerhalb einer for-Schleife verwendeten Variablen müssen zuvor deklariert worden sein, aber das kann auch vor der for-Schleife und damit außerhalb erfolgt sein.

Dieser Block darf auch mehrere Variablen durch Komma separiert initialisieren..

Der Block kann also auch komplett leer sein, muss aber zwingend mit einem einzigen Semikolon abgeschlossen werden.

0, 1 bis n Bedingungen

Der zweite Block im Kopf einer for-Schleife beinhaltet die Fortsetzungsbedingungen. Nur alle alle Fortsetzungenbedingungen erfüllt sind, wird die Schleife ein weiteres Mal oder zu Beginn überhaupt ein erstes Mal durchgeführt.

Dieser Block endet immer mit einem ; Semikolon und wird oftmals mit einem Konstrukt wie im Falle von Arrays mit i<arr.length gefüllt. Abbruchung

After-Loop-Block

Nach jedem einzelnen Durchlauf der Schleife wird ein dritter Block ausgeführt. Dieser Block wird zumeist dafür genutzt, die Laufvariable wie i beispielsweise um +1 über i++ oder i=i+1 zu erhöhen.

Auch dieser Block kann mehrere Anweisungen durch Komma separiert beinhalten, muss aber mit einem Semikolon abgeschlossen werden.

Beispiele für for-Schleifen

Zahlen von 0 bis 9



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

Das Komma separtiert die zwei Variablen

Das Beispiel zeigt, wie man eine Variable s als zweite Variable deklarieren und initialisieren kann, um beispielweise die Gesamtsumme einer Zahlen von 0 bis einschließlich 9 ermitteln zu können.

Da diese Variablen allerdings innerhalb der for-Schleife deklariert wurden, hat die Variable s außerhalb der Schleife keine Gültigkeit mehr.

Die Variable s verliert als mit der abschließenden Klammer ihre Gültigkeit.



for(int i=0, s=0; i < 10; i++) { // {#1}
 s += i;
 System.out.println(s);
} // {#2}

System.out.println(s); // {#3} FEHLER!

Fehlende oder nicht eintretende Abbruchbedingung

Ob gewollt oder ungewollt: Endlosschleifen entstehen immer dann, wenn die Bedingung in {#1} immer erfüllt ist und damit die Schleife über die Bedingung(en) im Kopf niemals abgebrochen werden wird.

Ohne eine Abbruchbedingung {#3} innerhalb der Schleife mit einer zugehörigen break Anweisung wird das Programm endlos ausgeführt bis es vom Anwender über das Betriebssystem abgebrochen oder der Rechner ausgeschaltet wird.



for(i=0;i>0;i++) { // {#1}
 System.out.println(i); // {#2}
 if(i>=100) { break; } // {#3}
}

// {#4}

Schleifen im Multithreading

Es gibt Fälle, in denen eine for-Schleife als Dauerschleife explizit gewünscht ist.

So wird im Falle von run-Methoden von Threads oder Runnables die run-Methode oft als Dauerschleife implementiert. Das gilt auch sinngemäß für Callables.

Die Abbruchbedingung wird hierbei außerhalb der for-Schleife und auch außerhalb der run-Methode gesetzt.

Threads sind ein eigenständiger Themenblock im Bereich Parallel Computing.



public void run() {
 int zaehler=0;
 for(;;) { // {#1}
 synchronized(Programm.sollAbbrechen) {
 if(Programm.sollAbbrechen) { break; }
 }
 System.out.println("Tick "+zaehler++);
 Thread.sleep(1000); // {#2}
 }
 // {#4}
}

 
package com.stuelken.java.a1.basics.arrays;

// @formatter:off
/**
 * A1-e01: Einfaches primitiver int[]-Array.
 * - Erzeugen
 * - Lesen
 * - Ändern
 * - Durchlaufen
 <pre>
nums[1] = 20
Alle Werte: 10, 20, 30 
 </pre>
 */
// @formatter:on
public class Array01Int {
 public static void main(String[] args) {
 // Erzeugen
 int[] nums = new int[3];
 nums[0] = 10;
 nums[1] = 20;
 nums[2] = 30;

 // Lesen
 System.out.println("nums[1] = " + nums[1]);

 // Durchlaufen
 System.out.print("Alle Werte: ");
 for (int i = 0; i < nums.length; i++) {
 System.out.print(nums[i] + (i < nums.length - 1 ? ", " : ""));
 }
 System.out.println();
 }
}

Deklaration von Variablen

Um einen Array in einer Variablen speichern zu können, ist eine Variable mit einem Bezeichner wie hier «num» erforderlich. Maßgebend ist aber auch eine Typisierung mit int[] mit Angabe des Typs gefolgt von zwecke eckigen Klammern.

Dimensionieren und Speicherplatz reservieren

Der für Arrays erforderliche Speicherplatz wird in JAVA sowie C# und C++ (im Gegensatz zu Sprachen wie PHP oder JS) bei der Erzeugung des Array-Objekts als Wert einmal dimensioniert und lässt sich anschließend nicht mehr ändern.

Eine Adressierung von Feldern eines Array ist also erst möglich, wenn Speicherplatz im Arbeitsspeicher angelegt und hierfür für die gewünsche Anzahl von Feldern im Array reserviert wurde.

length

Die Anzahl der Elemente, die ein Array hat, lässt sich über .length an der Variablen mit dem Array adressieren.

Auch dann, wir hier einen Array mit 3 Feldern für die Indizes 0 bis 2 anlegen, können die eigentlichen Werte für die jeweiligen Felder dennoch quasi leer sein, wobei bei int-Werte dann das Feld den Wert 0 hat, bei Objekten hinzegen null.

Klassische for-Schleife mit Zugriff über Index

Auch dann, wir hier einen Array mit 3 Feldern für die Indizes 0 bis 2 anlegen, können die eigentlichen Werte für die jeweiligen Felder dennoch quasi leer sein, wobei bei int-Werte dann das Feld den Wert 0 hat, bei Objekten hinzegen null.

JavaDoc und Eclipse: Vermeiden der Auto-Formatierung

Zur Vermeidung, dass das Code-Sample im <pre>..</pre> Tags bei bei Code-Format in der Eclipse-IDE von mehreren Zeilen in Fließtext umgewandelt wird, lässt sich mit // @formatter:off die Formatierung abschalten und später mit on wieder anschalten.

while-Schleife

Control-Flow-Statements | while-Schleife

Eine while-Schleife ist eine kopfgesteuerte Schleife welche vor der Ausführung einer Schleife stets prüft, ob die Bedingung erfüllt ist.

Fehlt diese Bedingung so ist diese automatisch erfüllt, so dass die while() { } zu einer Dauerschleife mutiert.




int counter = 0;

while(counter <= 10) {

 // A
 System.out.println(counter);

 counter++;

 if(counter==5) { continue; }
}

do-while-Schleife

Control-Flow-Statements | do-while-Schleife

Eine do-while-Schleife ist eine fußgesteuerte Schleife. Sie wird mindestens einmal ausgeführt, weil die zugehörige Bedingungsprüfung erst nach einem ersten Durchlauf erfolgt.

Die do-while-Schleife in vielen Ausbildungen Prüfungsstoff, weil man erwartet, dass jeder Entwickler den Unterschied zwischen kopf- und fußgesteuerten Schleifen kennt, auch wenn gefühlt 99,99995 aller Entwickler immer kopfgesteuerte Schleifen nutzen.



int i = 0;
do {
 System.out.println(i);
 i++;
} while (i<10);

if-elseif-else

Control-Flow-Statements | if-elseif-else

Ein if-elseif-else-Block ermöglicht Fallunterscheidungen.

Es muss zwingend immer einen if Block geben. Es kann 0 bis n else-if Blöcke und 0 bis 1 abschließenden else Block geben.



for (int i=0; i<10; i++) {
 if(i%2==0 && i%3==0) {
 System.out.println("Die Zahl i="+i+" ist gerade und durch 3 teilbar. ");
 } elseif (i%5==0) {
 System.out.println("Die Zahl i="+i+" ist durch 5 teilbar.");
 } elseif (i%7==0) {
 System.out.println("Die Zahl i="+i+" ist durch 7 teilbar.");
 } else {
 System.out.println("Die Zahl erfuellt keines der bisherigen Kriterien.");
 }
}

if-Kurznotation

Wenn der Programmcode je Block nur eine Zeile hat, darf formal betrachtet die geschweiften Klammern weglassen. Während die einen finden, dass der Code so leserlicher wird, vermissen andere für die Verbesserung der Lesbarkeit die Klammern.

Das Problem hierbei: Zu welchem Block zählt hier eigentlich der else-Zweig?

In Java bindet ein else immer zur nächstgelegenen, ungeschlossenen if-Anweisung. Fehlen geschweifte Klammern, steuert ein if nur exakt die unmittelbar folgende Anweisung; und nicht den eingerückten Block, den man optisch vielleicht erwartet.

Merke: Die Einrückung ist eine Code-Kosmetik und spielt für den Compiler, anders als bei Python, keine Rolle.



// "Schlank"
if (aNumber >= 0)
 if (aNumber == 0)
 System.out.println("first string");
else System.out.println("second string"); // {#3] Verwirrend eingerückt!


// "Vollständig"
if (aNumber >= 0) {
 if (aNumber == 0) {
 System.out.println("first string");
 } else {
 System.out.println("second string");
 }
 System.out.println("third string");
} 
 System.out.println("forth string");

switch

Control-Flow-Statements | switch

Mit switch, case, break und default lassen sich alternativ zu if/elseif/else-Statements auch Fallunterscheidungen programmieren.

Maßgebend zum Verständnis ist hierbei, dass die Variable, auf die auf Überstimmungen in den mit case definierten Bedinungen geprüft wird, innerhalb der Klammern des switch(..) Kopfes geprüft werden wird.

Trifft die Bedinungen in einem case Block im Sinne von case==fall zu, so wird der Programmcode ausgeführt, welcher nach dem case Element zu finden ist.

Mit diesem Verfahren ist es möglich, dass mehrere Fälle zur gleichen Code-Ausführung führen können, so dass bei Konsolen-Parametern beispielsweise oftmals eine Kurz- und eine Langfassung üblich ist.

Der break; Ausdruck bewirkt, dass der nach einem case ausgeführte Code an dieser Stelle abgebrochen und das Programm nach dem switch Block fortgesetzt wird. Das Vergessen von diesen break; Zeilen ist eine der größten Fehlerquellen bei Einsteigern.

Der default: Block ist der letzte Block innerhalb eines switch Blocks und optional, dh. man kann diesen auch weglassen. Dieser letzte Block dient dazu, dass switch stets einen Wert kontrolliert liefert oder einen Block ausführt, auch wenn sonst kein einziger Fall als case zutrifft.



// Gültig ab Java 7
String fall="--port";

switch(fall) {
 case "-p":
 case "--port":
 System.out.println("Port-Parameter erkannt!");
 break;
 case "-h":
 case "--help":
 default:
 System.out.println("-p, --port: Port");
 }

switch(intZahl) { }

switch(..) lässt sich mit int-Zahlen verwenden. Das gilt auch für byte, short, char und deren Wrapper-Klassen.



int zahl = 2;

switch (zahl) {
 case 1:
 System.out.println("Eins");
 break;
 case 2:
 System.out.println("Zwei");
 break;
 default:
 System.out.println("Andere Zahl");
}

Nicht unterstützt werden long, float, double und boolean.

switch(enumKey) { }

switch(..) lässt sich mit Enumerationskonstanten ab Java 5 verwenden.



enum Tag { MONTAG, DIENSTAG }

Tag tag = Tag.MONTAG;

switch (tag) {
 case MONTAG:
 System.out.println("Erster Werktag");
 break;
 case DIENSTAG:
 System.out.println("Zweiter Werktag");
 break;
}

switch(obj) { }

switch(..) ermöglicht KEINE Unterstützung von Referenztypen mit Ausnahme von Enumerationen und Strings.



package com.stuelken.java.a1.basics.e30.switchstring;

/**
 * 
 * @author t2m
 */
public class SwitchStringExample {

	/**
	 * Ruft {@link SwitchStringExample#main(String[])}.
	 * @param run Keine Parameter
	 */
	public static void main(String[] run) {

		String text = (new SwitchStringExample()).test();
		System.out.println(text);

	}

	/**
	 * Liefert Erläuterungstest für einen Befehl wie 
	 * "START" und "Beginne Prozess".
	 * 
	 * @return Erläuterungstext.
	 */
	public String test() {

		String befehl = "START";
		String text = "";

		switch (befehl) {
			case "START":
				text = "Beginne Prozess";
				break;
			case "STOP":
				text = "Beende Prozess";
				break;
			default:
				text = "Unbekannter Befehl";
		}
		return text;

	}

}

// Beginne Prozess

Wer Objekte auf Gleichheit prüfen möchte, muss auf deren Eigenschaften oder ggf. den Hashcode prüfen.



enum Status { OPEN, CLOSED, PENDING }

class Ticket {
 Status status;
 // Konstruktor, Getter…
}

Ticket t = new Ticket(Status.OPEN);

switch (t.getStatus()) {
 case OPEN:
 System.out.println("Ticket offen");
 break;
 case CLOSED:
 System.out.println("Ticket geschlossen");
 break;
 default:
 System.out.println("Anderer Status");
}

Dispatch über Maps

Control-Flow-Statements | Dispatch über Maps

Im Zuge des Ausbaus der JAVA Collection API um Streams setzen viele Entwickler bei Schleifen und dem Prüfen von Bedingungen nicht mehr auf die eigentlichen Grundlagen wie auch switch sondern auf Collection Klassen in Verbindung mit .stream() und anderen Alternativen. Ein Grundverständnis der Basics ist aber weiterhin zwingend erforderlich.

Hinweis: Die Voraussetzung für das Arbeiten mit Collections ist ein tieferes Verständnis der Objektorientierung, der generischen Programmierung sowie des Prinzips von Interfaces, so dass Collections und Schleifen über Streams erst in späteren Themenblocken erklärt werden.

Klasse für Objekte

Um Objekte vergleichen zu können, benötigen wir zu Beginn eine zugehörige Klasse.



package com.stuelken.java.a1.basics.e34.switchmapcollectionstream;


/**
 * Eine Klasse welche {@link Vehicle} Objekte anhand 
 * von {@link VehicleType} Enumerationen unterscheiden kann. 
 * 
 * Diese Klasse dient nur als Beispiel
 * 
 * @author t2m
 */
class Vehicle {
	private final VehicleType type;
	private final double weight;
	private final double trailerWeight; // nur für LKW relevant

	public Vehicle(VehicleType type, double weight, double trailerWeight) {
		this.type = type;
		this.weight = weight;
		this.trailerWeight = trailerWeight;
	}

	public VehicleType getType() {
		return type;
	}

	public double getWeight() {
		return weight;
	}

	public double getTrailerWeight() {
		return trailerWeight;
	}
}

Berechnung der Nutzlast

Daa Programm ermittelt die gesamte Nutzlast aller Fahrzeuge.



package com.stuelken.java.a1.basics.e34.switchmapcollectionstream;

import java.util.List;

public class SwitchMapCollectionStreamExample {

	public static void main(String[] args) {

		List<Vehicle> fleet = List.of(
		 new Vehicle(VehicleType.CAR, 1500, 0),
		 new Vehicle(VehicleType.TRUCK, 8000, 2000),
		 new Vehicle(VehicleType.MOTORCYCLE, 200, 0),
		 new Vehicle(VehicleType.TRUCK, 9000, 2500));

		double totalLoad = VehicleAggregator.computeTotalLoad(fleet);
		System.out.printf("Gesamte Nutzlast der Flotte: %.2f kg\n", totalLoad);
	}
}


package com.stuelken.java.a1.basics.e34.switchmapcollectionstream;

import java.util.Collection;

public class VehicleAggregator {

	/**
	 * Berechnet die gesamte Nutzlast aller Fahrzeuge. - CAR : nur Eigengewicht -
	 * TRUCK : Eigengewicht + Anhängergewicht - MOTORCYCLE: nur Eigengewicht
	 */
	public static double computeTotalLoad(Collection<Vehicle> vehicles) {
		return vehicles // Collection 
				.stream() // Stream<Vehicle> mit Vehicle Objekten
		 .mapToDouble(v -> {
			 switch (v.getType()) {
				 case CAR:
					 return v.getWeight();
				 case TRUCK:
					 return v.getWeight() + v.getTrailerWeight();
				 case MOTORCYCLE:
					 return v.getWeight();
				 default:
					 return 0.0;
			 }
		 })
		 .sum(); // Aggregation eines Gesamtwerts
	}
}

Branching-Statements

Control-Flow-Statements | Branching-Statements

Branching-Statements steuern den Programmfluss und unterbrechen die sequentielle Ausführung. Sie gehören zu den Control-Flow-Statements und damit NICHT zu den Scopes (Sichtbarkeitsbereich, Gültigkeitsbereich, Lebensdauer von Variablen) von Code-Blöcken.

Die wichtigsten Branching-Statements sind sogenannte bedingte Verzweigungen wie if, else if, else, switch, case mit default.

Sprung-Anweisungen sind mitunter break, continue, return für Abbruch oder Ende einer Methode sowie sowie throw für das Auslösen einer Exception.

JAVA unterstützt break label; und continue label;.

Die herkömmlichen goto-Anweisungen, die man in der Frühzeit in anderen Programmiersprachen hatte, kennt man in Java nicht, da das auf Zeilennummern basierende Springen keinen Sinn mehr macht.


break



for (int i = 1; i <= 10; i++) {
 if (i == 5) {
 System.out.println("Abbruch bei i = " + i);
 break; // verlässt die Schleife komplett
 }
 System.out.println(i);
}
// Ausgabe: 1 2 3 4 Abbruch bei i = 5

continue



for (int i = 1; i <= 5; i++) {
 if (i % 2 == 0) {
 continue; // überspringt den aktuellen Durchlauf
 }
 System.out.println(i);
}
// Ausgabe: 1 3 5

Schleifen mit Labels



// Äußere Schleife
outer: 
for (int row = 1; row <= 3; row++) {
 // Innere Schleife
 inner:
 for (int col = 1; col <= 3; col++) {
 if (row == 2 && col == 2) {
 System.out.println("Break outer bei 2,2");
 break outer; // Springt aus beiden Schleifen heraus
 }
 System.out.printf("(%d,%d)%n", row, col);
 }
}

Control-Flow-Statements | for-in-Schleife

Im Zusammenhang mit Collections gibt es weitere Schleifen. Diese lassen sich auch mit Arrays verwenden, indem Arrays in eine entsprechende Collection gewandelt wird.

HINWEIS: Collections sind ein eigener Themenblock.



Collection collection = new ArrayList();
...
for (ItemType item : collecion) {
 ...
}

Control-Flow-Statements | switch-Expressions (Java 14)

Switch-Expressions wurden mit Java 14 (JEP 361) endgültig in die Sprache aufgenommen. Sie erweitern das klassische switch um zwei unabhängige Features: Die Pfeilnotation mit dem Array-Operator -> statt dem bisherigen : Doppelpunkt in Kombination mit break;, so dass die Anzahl der vergessenen break-Anweisungen in Zukunft keine Probleme machen wird.

Arrow-Notation ohne Fall-Through: case A, B -> ... lässt sich direkt für mehrere Konstanten abfragen, ohne dass man noch das vormalige break; benötigt.

Switch als Expression: Damit ist jetzt switch direkt ein Ausdruck und erzeugt direkt einen Rückgabewert, kann also innerhalb einer anderen Anweisung verwendet werden.

yield liefert das Ergebnis eines switch-Ausdrucks zurück, auch wenn wie bisher den Doppelpunkt nach dem case verwendet.

Beispiel



Day day = Day.WEDNESDAY;

String type = switch (day) {
 case MONDAY, FRIDAY, SUNDAY -> "Weekend";
 case TUESDAY -> "Busy day";
 default -> "Midweek";
};

System.out.println(type); // Midweek

Beispiel



int result = switch (day) {
 case SATURDAY, SUNDAY -> {
 System.out.println("Enjoy!");
 yield 0;
 }
 default -> {
 System.out.println("Work…");
 yield 1;
 }
};

System.out.println("result:"+result);

Control-Flow-Statements | Branching in Streams

Collections und Streams sind fortgeschrittene Themen. Ein Kurzeinblick ist aber auch im Einstieg deshalb sinnvoll, um bereits von Beginn an zu wissen, dass es die Programmsteuerung in anderer Form auch weiterhin in der Collection-Stream-API geben muss.

Hinweis: Dieses Beispiel gehört zur JAVA-C2 Ebene.



List<String> args = List.of("-p","8080","--name","Server42");

Map<String,String> params = IntStream.range(0, args.size()/2)
 .mapToObj(i -> Map.entry(args.get(2*i), args.get(2*i+1)))
 .collect(Collectors.toMap(
 Map.Entry::getKey, Map.Entry::getValue,
 (oldV,newV) -> newV, LinkedHashMap::new
 ));

String port = params.entrySet().stream()
 .filter(e -> e.getKey().equals("-p") || e.getKey().equals("--port"))
 .map(Map.Entry::getValue)
 .findFirst()
 .orElse("80");

FootNotes, Keywords, Tags


    java/Callables, java/Eclipse-IDE, java/ParallelComputing, java/Runnables, java/Threads, java/Wrapper-Klassen

    Hinweise, Rechte, Marken

    UI ORGANIZED.

    UIO3 Es ist einfacher als Du denkst.

    Stelle noch heute Deine Anfrage.

    uio--WebPageFooter-Module