Packages, Konstante, Zufallszahlen und Einlesen von Tastatur

1. Java Packages

Packages dienen zur Sicherstellung der Eindeutigkeit von Klassen. Z.B. wird der Klassenname Circle sicher in vielen Projekten verwendet, in denen geometrische Objekte benötigt werden.

Das Problem tritt vor allem dann zutage, wenn externe Bibliotheken im eigenen Projekt verwendet werden – und das ist in praktisch JEDEM größeren Progrmmierprojekt der Fall (es gibt u.a. eine sehr große Anzahl von sehr hochwertigen "Frameworks" und Bibliotheken aus dem OpenSource-Bereich).

Die Lösung besteht in einer Hierarchie von Packages ähnlich Ordnern im Dateisystm.
Als Trennzeichen dient der Punkt. Beispiel: java.util.Random. Das letzte Element Random ist der eigentliche Klassenname, das ganze der "voll qualifizierte Klassenname". Das Package ist java.util. Die Gliederung ist üblicherweise zuerst nach verwaltender Organisation, dann thematisch aufgebaut.

Jede Klasse hat also einen voll qualifizierten Klassennamen.
In unseren eigenen Projekten sind die Klassennamen, weil derzeit im "Default Package", gleichzeitig die voll qualifizierten Namen. Im allgemeinsten Fall muss dieser verwendet werden, um eine Klasse eindeutig "lokalisieren" zu können.

Technisch sind die Packages (bis auf exotische Ausnahmen) als Ordner-Struktur umgesetzt.
Auch das JDK selbst ist in Packages gegliedert.

Eigene Projekte sollten normalerweise ebenfalls im Packages strukturiert sein.
Nur innerhalb von BlueJ verzichten wir darauf, da hier Packages sehr unpraktisch zu handhaben sind.

Als Erleichterung besteht die Möglichkeit, Klassen zu "importieren", dann reicht der normale Klassenname zum Ansprechen aus (außer es gibt eine Namenskollision – es werden in einer Klasse zwei gleichnamige andere Klassen aus unterschiedlichen Packages verwendet).

Das eigene Package-Deklaration (der aktuellen Java-Datei) steht, wenn nicht – wie bisher bei uns üblich – das Default-Package (quasi das "Wurzelverzeichnis") verwendet wird, ganz oben:
package my.own.pkg;

Danach (noch oberhalb des Klassen-Kopfes) die Importe:
import java.util.Random; // wenn benötigt
import java.util.Scanner; // wenn benötigt

Alle Klassen aus Package java.lang werden automatisch importiert – z.B.

  • java.lang.System

  • java.lang.Math

(Zusatz-Info: Auch das Package des aktuellen Java-Files wird automatisch importiert, daher können alle Klassen aus dem selben Package mit dem "nackten" Klassennamen ohne Package aufgerufen werden.)

Wenn man möchte, kann man trotz Import auch den voll qualifizierten Namen verwenden:
java.lang.System.out.println("wie unnötig!");

Fur jetzt genügt es zu wissen, dass im JDK auf oberster Ebene im Wesentlichen folgende 2 Packages existieren: java und javax. Davon treten für uns derzeit nur 3 Unter-Packages von java auf: java.lang, java.util und java.time. Die Packages unter javax enthalten komplexere Funktionalitäten, die wir erst wesentlich später (teilweise) kennenlernen werden. (die Verwendung dieser Wurzel-Packages für eigene Package-Hierarchien ist für normale Programmierer "verboten")

Zur Vereinfachung des Zugriffs auf häufig genutzte JDK-Klassen wie java.lang.System (z.B. für System.out.println(), das eigentlich mit java.lang.System.out.println() angesprochen werden müsste) werden vom Compiler alle Klassen aus Package java.lang automatisch importiert – z.B. java.lang.System, java.lang.String, java.lang.Math.

Der im folgenden benötigte Zufallszahlengenerator liegt aber woanders - daher müssen wir für den Zugriff per Kurzname oben in der Source-Datei (oberhalb von public class …​) ein "Import-Statement" schreiben:
import java.util.Random;
Damit reicht im weiteren Random, um die Klasse des Zufallszahlengenerators anzusprechen.

Für Packages gelten die selben grundsätzlichen Namensregeln wie für Klassen, Variable und Methoden: alle Groß- und Kleinbuchstaben und Unterstrich, ab der 2. Stelle auch Ziffern (Umlaute etc. sind syntaktisch erlaubt, aber durch Konventionen verboten, da auf verschiedenen Betriebssystemen unterschiedliche "Encodungs" verwendet werden und damit bei Systemwechsel seltsame Sondereichen statt z.B. Umlaut angezeigt werden).

Laut Konvention werden Package-Namen in Kleinbuchstaben geschrieben, bei Bedarf wird der Unterstrich '_' zur Teilwort-Trennung verwendet.

Um weltweite Eindeutigkeit zu erreichen ist es üblich, zu Beginn den umgekehrten Domain-Namen der Organisation zu schreiben, danach organisationsinterne Strukurierung. Ein Beispiel: at.spengergasse.rx2223_1xhif.lab17 oder at.htlw5.rx2223_1xhif.demo31pkgs.
Für unsere Labor-Beispiele würde reichen: my.pkg (wir müssen nicht gemeinsam ein großes Projekt umsetzen)

2. Konstante

Sehr häufig werden fixe Werte benötigt – prominentes Beispiel: der fundamentale mathematische Wert π (pi). Dieser ist als Konstante Math.PI verfügbar. Dabei fällt auf, dass 'PI' ausschließlich in Großbuchstaben geschrieben ist.

Generell werden Konstante komplett in Großbuchstaben benannt - zur Teilwort-Trennung dient der Unterstich '_'.

Da Konstante für alle Objekte den gleichen Wert haben, ist es sinnlos, in jeder Instanz Speicherplatz dafür zu "opfern". Stattdessen werden diese nur ein einziges Mal für die ganze Klasse gespeichert. Dazu dient das Schlüsselwort static.

Zusätzlich sind sie unveränderlich. Um dies auszudrücken, gibt es das Schlüsselwort final.

Häufig werden Konstante auch "öffentlich zugänglich" (Schlüsselwort public) gemacht (Änderung von außen kann ohnehin nicht geschehen).

Natürlich können sie JEDEN Typ (primitive und Objekt-Typen) haben.

Da späteres Setzen des Wertes nicht möglich ist, müssen sie immer sofort ihren Wert erhalten.

Verwendung von Konstanten in anderen Klassen erfordert das Voranstellen des Klassennamens der definierenden Klasse, konkret wie hier ersichtlich:
System.out.println(Person.UNDEFINED_NAME)
In Klasse Person selbst reicht der Konstantenname allein.

Beispiele:

  • public static final int MAX_VALUE = 17;

  • public static final String UNDEFINED_NAME = "--Kein-Name--";

  • public static final Person DEFAULT_PERSON = new Person("Max", "Mustermann");

3. Zufallszahlen-Erzeugung

3.1. Überblick Zufallszahlen in Java

Erstaunlich wichtig ist in der Praxis das Erzeugen von Zufallszahlen. Plakative Anwendungen sind z.B.:

  • Organisation des Datenpaket-Sendens in Netzwerken

  • Erzeugen global eindeutiger Identifikationen für Dateien, Nutzer, Geräte, etc.

  • Automatisiertes Erzeugen von Passwörtern

  • etc.

Daher gibt es in vermutlich jedem komplexen Betriebssystem und in jeder Programmierplattform (wie Java) Zufallszahlen-Generatoren. Diese liefern häufig Pseudo-Zufallszahlen (durch Verwenden eines Parameters "Seed" kann die Folge "beinahe-zufällig" gemacht werden). Zum Generieren echter Zufallszahlen kann in manchen Fällen der Zufallszahlen-Generator des Betriebssystems genutzt werden, anderenfalls eignen sich Kombinationen z.B. des exakten Zeitpunkts der Anforderung (oft in Nanosekunden), von Netzwerk-Kennungen, aktueller Temperatur, sonstiger physikalische Zustände oder spezieller Hardware.

Java bietet gleich mehrere Implementierungen:

  • java.security.SecureRandom (wird hier nicht besprochen, liefert echte oder "fast-echte" Zufallszahlen)

  • die Methode java.lang.Math.random() (kurz: Math.random()), die double-Zufallszahlen zwischen 0.0 (inclusive) und 1.0 (exclusive) liefert, also 0.0 bis 0.999999…​.

  • die Klasse java.util.Random, die flexibel einsetzbare Zufallszahlengenerator-Objekte erzeugen lässt. Hier lassen sich Zufallszehlen verschiedenen Typs erstellen.
    DIESE Variante werden wir im weiteren nutzen.

  • weitere.

Ein per Random rndGen = new Random() erzeugtes Zufallszahlengenerator-Objekt (java.util.Random) hat Methoden, Zufallszahlen verschiedenenen Typs zu erzeugen. Sehr praktisch und im weiteren verwendet ist die Methode nextInt(…​): mit int limit = 3; rndGen.nextInt(limit) liefert sie bei jedem Aufruf eine neue Zufallszahl mit einem der 3 Werte 0, 1 oder 2.

Der Wert von limit legt also die "Spannweite" fest. Falls die Untergrenze nicht 0 sein soll, kann zur gelieferten Zufallszahl der Wert der Untergrenze addiert werden: rndGen.nextInt(limit) - 2 liefert die Zufallszahlen -2, -1, 0.

Wenn die kleinste und größte zu bildende Zufallszahl als int minZz, maxZz gegeben ist, liefert folgender Ausdruck das Gewünschte: int zz = minZz + rndGen.nextInt(maxZz - minZz + 1); das gewünschte.

Einfaches Beispiel, in dem so lange Zufallszahlen im Bereich 0 bis (incl.) 4 generiert werden, bis die Zahl 3 auftritt:

3.2. Beispiel zu 'java.util.Random' für ganzzahlige Zufallswerte 0 bis 4

import java.util.Random;

public class ZufallszahlenDemo {

    public void zzIntroDemo() {
        Random zufallsZahlenGenerator = new Random();
        boolean ok = true;
        while (ok) {
            int zufallsZahl = zufallsZahlenGenerator.nextInt(5); // Zahlen 0, 1, 2, 3, 4 möglich
            System.out.print("." + zufallsZahl);
            if (zufallsZahl == 3) {
                ok = false;
                System.out.println(".");
            }
        }
    }
}

Es wird zufällg z.B. geliefert:

.2.1.0.1.4.4.0.0.3.

oder:

.4.1.3.

In jedem Fall ist die letzte angezeigte Zahl 3, da bei Auftreten dieser abgebrochen wird. Theoretisch könnten auch tausende Zahlen auftreten (wenn sehr lange zufällig keine 3 generiert wird), was aber extrem unwahrscheinlich ist.

3.3. Beispiel ganzzahlige Zufallswerte mit frei wählbarem Wertebereich

Nun adaptieren wir obiges Beispiel so, dass die kleinste und größte Zufallszahl als Parameter übergeben werden können. Auch die "Stop-Zahl" wird als dritter Parameter steuerbar (muss naturgemäß im "erlaubten" Zahlenbereich liegen, wird hier nicht geprüft).

import java.util.Random;

public class ZufallszahlenDemo {
    /** minZz, maxZz  ... minimale/maximale Zufallszahl;  stopZahl ... Wert, bei dem
      * Schleife beendet wird (darf nicht außerhalb des erlaubten Bereiches liegen!)
      * Methode z.B. als 2. in obige Klasse einfügen
      */
    public void zzFlexBereichDemo(int minZz, int maxZz, int stopZahl) {
        Random zufallsZahlenGenerator = new Random();
        boolean ok = true;
        System.out.println("");
        while (ok) {
            int spannweite = maxZz - minZz + 1;  // explizit gemacht für Debugging
            int zzRoh = zufallsZahlenGenerator.nextInt(spannweite);  // Detto
            int zufallsZahl = minZz + zzRoh;
            //int zufallsZahl = minZz + zufallsZahlenGenerator.nextInt(maxZz - minZz + 1);
            System.out.print("." + zufallsZahl);
            if (zufallsZahl == stopZahl) {
                ok = false;
                System.out.println(".");
            }
        }
    }
}

Bei Aufruf mit zzFlexBereichDemo(3,7,5) wird zufällig z.B. geliefert:

.3.5.

oder (der letzte Wert ist immer die stopZahl):

.7.4.3.6.3.3.6.6.4.5.

3.4. Boolean Zufallswerte

Unser oben genutzter Zufallszahlen-Gnerator kann auch Zufallswerte anderen Typs erzeugen (kann abwechselnd unterschiedliche Zufallswert-Typen liefern). Hier ein Beispiel mit Boolean-Werten (nur die Methode, kein Import-Statement und kein 'public class …​'):

    public void boolZufallsWerteDemo(int anzahl) {
        Random zufallsZahlenGenerator = new Random();
        int anzTrue = 0;  // Zählen der 'true'-Werte
        for (int i = 0; i < anzahl; i++) {   // For-Schleife (wie while + "rundherum")
            if (i > 0 && i%10 == 0) {  // nach jeden 10. Wert:
                System.out.println();  // Zeilenschaltung
            }
            boolean val = zufallsZahlenGenerator.nextBoolean();
            System.out.print(val + " ");
            if (val == true) {
                anzTrue++;  // hochzählen, da val 'true' ist
            }
        }
        System.out.println("\nAnzahl: " + anzahl + ", davon 'true': " + anzTrue);
    }

3.5. Double-Zufallswerte zwischen 0.0 und 1.0

Nun zwei Beispiele für Zufallswerte vom Typ double. Das erste, nachstehend gezeigte produziert Werte zwischen 0.0 (inclusive, d.h. 0.0 kann tatsächlich auftreten) und 1.0 (exclusive, d.h. 1.0 ist die erste Zahl, die NICHT auftreten kann - die durch die Genauigkeitsgrenzen von Double-Zahlen höchstmöglich auftretende Zahl ist 0.999_999_999_999_999_9 …​ Unterstrich zur Tausendertrennung in Java erlaubt!).

    public void dblZufallsZlDemo1(int anzahl) {
        Random zufallsZahlenGenerator = new Random();
        double summe = 0;
        for (int i = 0; i < anzahl; i++) {   // For-Schleife (wie while + "rundherum")
            if (i > 0 && i%5 == 0) {  // i > 0, damit nicht schon im ersten Durchlauf
                System.out.println();
            }
            double dblZz = zufallsZahlenGenerator.nextDouble();
            summe += dblZz;
            System.out.print(dblZz + "  ");
        }
        System.out.println("\nMittelwert: " + summe/anzahl);
    }

Der Mittelwert (summe/anzahl) sollte sich für größere Anzahl (z.B. anzahl = 10000) meistens schon recht gut an 0.5 annähern.

3.6. Double-Zufallswerte in beliebig festlegbarem Bereich

Hier das oben angekündigte 2. Beispiel.

Der erste Parameter minZzIncl gibt an, was die kleinste erlaubte Zahl ist. Diese kann tatsächlich auftreten, ist also 'inclusive'.

Der zweite Parameter maxZzExcl kann "beinahe" erreicht werden, ist also 'exclusive'.

Diese Logik (Untergrenze inclusive, Obergrenze exclusive) liegt im Generator und ist konsistent mit einem allgemeinen (sinnvollen) Designprinzip vieler Funktionalitäten im JDK.

Die Roh-Zufallszahlen dblZzRaw zwischen 0.0 und 1.0 werden einfach "gestreckt" (multipliziert mit Bereichsgröße) und "verschoben" (Untergrenze addiert), um in dblZz beliebige Wertebereiche zu erhalten.

    public void dblZufallsZlDemo2(double minZzIncl, double maxZzExcl, int anzahl) {
        Random zufallsZahlenGenerator = new Random();
        double summe = 0;
        for (int i = 0; i < anzahl; i++) {
            if (i > 0 && i%5 == 0) {
                System.out.println();
            }
            double dblZzRaw = zufallsZahlenGenerator.nextDouble();
            double dblZz = minZzIncl + dblZzRaw*(maxZzExcl - minZzIncl);
            summe += dblZz;
            System.out.print(dblZz + "  ");
        }
        System.out.println("\nMin: " + minZzIncl + ", Max: " + maxZzExcl
                + "; Mittelwert: " + summe/anzahl);
    }

3.7. Nutzen von Zufallszahlen zum Generieren zufälliger Texte

Hier wird zufälliger Text generiert. Der Einfachheit halber nur Kleinbuchstaben incl. ä, ö, ü, ß, dazu '-', Zeilenschaltung '\n' und Leerzeichen (diese 3 x so häufig, um durchschnittlich kürzere Worte zu erzeugen)

    public void zufallsText(int laenge) {
        Random zufallsZahlenGenerator = new Random();
        String txt = "";
        int zeilen = 1;
        int leerzeichen = 0;
        for (int i = 0; i < laenge; i++) {
            int zz = zufallsZahlenGenerator.nextInt(35); //a-z äöüß '-' 1x'\n', 3x' '
            char ch = ' ';
            if (zz < 26) {  // US-Alphabet hat 26 Zeichen, addieren zu Wert von 'a':
                ch = (char)('a' + zz);  // Wandeln des int-Ergebnises auf char
            } else if (zz == 26) {
                ch = 'ä';
            } else if (zz == 27) {
                ch = 'ö';
            } else if (zz == 28) {
                ch = 'ü';
            } else if (zz == 29) {
                ch = 'ß';
            } else if (zz == 30) {
                ch = '-';
            } else if (zz == 31) {
                zeilen++;
                ch = '\n'; // gelegentlich Zeilenschaltg -> besser "lesbar"
            } else {   // letzte Möglichkeiten 32, 33, 34: bleibt auf Leerzeichen,
                leerzeichen++; //.. damit 3x häufiger Leerzeichen -> kürzere Worte
            }
            txt += ch;
        }
        System.out.println("\nGenerierter Text, " + laenge + " Zeichen, " + zeilen
                + " Zeilen, " + leerzeichen + " Leerzeichen:\n[" + txt + "] \n");
    }

4. Interaktives Einlesen von Daten mit Klasse 'Scanner'

Bisher konnten wir dank der besonderen Fähigkeiten von BlueJ quasi-interaktiv einzelne Methoden (unter Eingabe benötigter Parameter) aufrufen. In "normalen" Java-Programmen besteht diese Möglichkeit ohne großen Aufwand (Graphisches User-Interface etc.) nicht.

Da jedoch extrem häufig Bedarf dafür besteht, auch auf der "Kommandozeile" interaktiv zu arbeiten, gibt es Möglichkeiten dazu, innerhalb der Methoden quasi als Gegenstück zur Ausgabe mit System.out.println(…​) u.ä. auch Daten einzulesen.

Eine für uns gut nutzbare Möglichkeit bietet die Klasse java.util.Scanner.

Zur Nutzung sind 6 Schritte nötig/empfehlenswert:

  • Importieren (oder überall Verwendung des voll qualifizierten Namens java.util.Scanner)

  • Scanner instanzieren mit Scanner sc = new Scanner(System.in);

  • Scanner konfigurieren (optional: Setzen des Token-Separators, Setzen des "Komma"-Zeichens – siehe unten)

  • Prüfen, ob als nächstes ein passender Token (if (hasNextInt()) { …​ } o.ä.) anliegt

  • wenn ja: konsumieren mit (int value = nextInt(); o.ä.)

  • nach letzter Verwendung: "schließen" des Scanners (bei Programm-Ende geschieht das automatisch, bis dahin sind aber unnötig Systemressourcen gebunden)

4.1. Erstes Beispiel zum Zahlen-Einlesen (int)

Im folgenden Beispiel wird der Einsatz demonstriert:

import java.util.Scanner;                          (1)

public class ScannerDemo {

    public void scannerIntValDemo1() {
        Scanner scanner = new Scanner(System.in);  (2)
        String stopToken = null;
        System.out.println("===== Demo Ganzzahl-Eingabe =====");
        boolean repeat = true;                     (3)
        while (repeat) {
            System.out.print("  Ganzzahl (Abbruch, wenn ungültige Eingabe): ");
            if (scanner.hasNextInt()) {            (4)
                int intVal = scanner.nextInt();    (5)
                String negPosInfo = ">=0";
                if (intVal < 0) {                  (6)
                    negPosInfo = "negativ";
                }
                System.out.println("    Eingegeben: " + intVal + " (" + negPosInfo + ")");
            } else {
                stopToken = scanner.next();        (7)
                repeat = false;                    (8)
            }
        }
        scanner.close();                           (9)
        System.out.println("    ## Beendet durch \"" + stopToken + "\"");
    }
}
1 Import der Klasse, damit sie per Kurzname Scanner im Code ansprechbar wird (sonst müsste immer stattdessen java.util.Scanner geschrieben werden)
2 Erzeugen eines Scanner-Objekts, das von der Tastatur (Standard-Input) einliest
3 Boolean Variable (mit true initialisiert) als explizite Fortsetzungsbedingung, die bei Bedarf im Code auf false umgeschaltet wird
4 Das Scanner-Objekt "schnuppert", ob als nächstes eine Int-Zahl bereitsteht (bleibt in der "Warteschlange"). Wenn nichts bereitsteht, wartet die Methode auf weiteren Input, blockiert also den Programmlauf, bis IRGENDEINE Eingabe vorliegt. Diese wird dann geprüft. Bei true wird die Zahl eingelesen, bei false der Abbruch vorbereitet.
5 Tatsächliches Einlesen der Integer-Zahl (und Entfernen von der "Warteschlange")
6 Reaktion auf negative Zahl - Erläuterungstext wird passend gesetzt
7 Es liegt ein Nicht-Ganzzahl-Input vor. Der Wert wird eingelesen, egal, was er enthält, damit am Schluss ausgegeben werden kann, wodurch das Ende verursacht wurde (könnte auch eine "Komma-Zahl", eine Zahl außerhalb des int-Wertebereiches oder ein explizites "Datei-End-Zeichen" gewesen sein: Linux/Mac: [d], Win: [ctrl][z],[enter])
8 Die Fortsetzungsbedingung wird umgesetzt auf false, sodass bei der nächsten Prüfung die Schleife verlassen wird.
9 Ein Scanner bindet Systemressourcen, die nach Nutzung wieder freigegeben werden sollten. Das geschieht hiermit.

4.2. Einlesen nicht-ganzer Zahlen

Im Prinzip funktioniert dies analog, statt hasNextInt() etc. verwendet man hasNextFloat() oder hasNextDouble() etc. .
WICHTIG: Zu beachten ist dabei, dass das Komma üblicherweise – bei in Östereich für Windows, Apple, Linux gesetzten Locale-Einstellungen – der Beistrich ist. Wenn der (englische) Punkt gewünscht wird, kann dies z.B. mit
scanner.useLocale(Locale.ENGLISH); erreicht werden.

4.3. Text-Menus zur interaktiven Programm-Steuerung

Nachstehend ein Eingabe-Text-Menü, wo erst durch Eingabe eines Buchstabens die Aktion ausgewählt wird und anschließend entweder (je nach Auswahl) ein Text oder eine Ganzzahl eingegeben wird.

Die Reaktion ist hier nur die Ausgabe des eingegebenen Wertes, allerdings kann stattdessen jede beliebige Aktivität erfolgen. Auf diese Weise kann ein interaktives Programm erstellt werden.

import java.util.Scanner;

public class TextMenuDemo
{
    private Scanner scanner;
    private static final String SINGLE_CHAR_REGEX = "[0-9A-Za-zÄÖÜäöüß]{1,1}";  (1)

    public void runMainMenu() {
        scanner = new Scanner(System.in);
        boolean loop = true;
        while (loop) {
            // Ausgabe der Auswahl-Möglichkeiten
            System.out.println();
            System.out.println("===== Menü =====");
            System.out.println("t ... Text eingeben");
            System.out.println("i ... Int-Zahl eingeben");
            System.out.println("q ... ENDE (quit)");
            System.out.print(" --> (t|i|q)[RETURN] eingeben: ");
            if (scanner.hasNext(SINGLE_CHAR_REGEX)) {                           (2)
                String choice = scanner.next(SINGLE_CHAR_REGEX);                (3)
                String garbage = scanner.nextLine();                            (4)
                if (!garbage.isBlank()) {                                       (5)
                    System.out.println("    !! Ungueltiger Auswahl-Folgetext '"
                            + garbage + "'");
                }
                // Auswertung Menü-Auswahl:
                if (choice.equals("q")) {                                       (6)
                    loop = false;
                    System.out.println("Programm wird beendet ...");
                } else if (choice.equals("t")) {
                    handleChoiceTxt();                                          (7)
                } else if (choice.equals("i")) {
                    handleChoiceInt();                                          (8)
                } else {
                    System.out.println(" !! Ungültiges Auswahl-Zeichen: '" + choice + "'");
                }
            } else {
                String badInput = scanner.nextLine();                           (9)
                System.out.println(" !! Ungültige Eingabe: '" + badInput + "'");
            }
        }
        scanner.close();                                                        (10)
    }

    private void handleChoiceTxt() {
        System.out.print("   --> Text [RETURN]: ");
        String txt = scanner.next();                                            (11)
        txt += scanner.nextLine();                                              (12)
        System.out.println("\n    Eingegebener Text: '" + txt + "'");
    }

    private void handleChoiceInt() {
        boolean loop = true;
        while (loop) {
            System.out.print("   --> Int-Wert [RETURN]: ");
            if (scanner.hasNextInt()) {                                         (13)
                int gzahl = scanner.nextInt();                                  (14)
                System.out.println("\n    Eingegebene Zahl: " + gzahl);
                loop = false;
            } else {
                String badInput = scanner.nextLine();                           (15)
                System.out.println("    !! ungültige Eingabe (kein Int-Wert): " + badInput);
            }
        }
    }
}
1 Regulärer Ausdruck (Formulierungs-"Sprache" für komplexe Text-Muster): in [] …​ erlaubte Zeichen, in {} …​ von-bis-Anzahl.
Hier eine einzelne Ziffer oder ein einzelner Klein- oder Großbuchstabe (samt Umlauten und 'ß').

Der Reguläre Ausdruck wird in einer Konstanten gespeichert.

2 "Schnuppern", ob der nächste "Token" ein dem regulären Ausdruck entsprechendes Textstück (Token) ist. Token = logisches Element, standardmäßig mit "Whitespace" (Leerzeichen ' ', Tabulator '\t' oder Zeilenschaltung '\n') getrennt.
3 Nun wird der vorher "erschnupperte" Token konsumiert
WICHTIG: scanner.hasNext() und scanner.next() warten wenn nötig, bis eine Eingabe (mit Zeilenschaltung abgeschlossen) vorliegt!!!
4 Falls nach dm Whitespace weitere Eingabe anliegt, wird diese sicherheitshalber "entsorgt", da sonst ein u.U. nachfolgendes wählerisches nextInt() o.ä. den Müll vorfindet und nicht mehr "weiterkann".
5 txt.isBlank() prüft, ob im String lesbarer Text vorliegt (nicht nur Whitespace)
6 'q' steht für "quit" = Beenden
7 Die Eingabe des Texts wird in einer separaten Methode abgewickelt, um den Menü-Code nicht mit anderen Aufgaben zu "verschmutzen".
8 Analog die Eingabe einer Zahl.
9 Falls kein dem Regulären Ausdruck entsprechender Token vorgefunden wurde, sondern etwas anderes (irgendetwas muss "hereingekommen" sein, sonst würde scanner.hasNext() immer noch warten), muss dieses entsorgt werden, da es sonst im Eingabepuffer liegenbleibt und bei jedem neuen Menü-Versuch vergeblich zu lesen versucht wird.
10 Ein Scanner bindet Systemressourcen, die nach Nutzung wieder freigegeben werden sollten. Das geschieht hiermit.
WICHTIG: die privaten Hilfsmethoden können somit nicht separat ausgeführt werden, da dann in diesem Beispiel der Scanner nicht "geöffnet" ist!
11 Hier ist kein vorheriges scanner.hasNext() nötig, da ein scanner.next() ohne Regulären Ausdruck als Parameter vollständig anspruchslos ist und alles "frisst", was es vorfindet.
12 falls auch Whitespace mit u.U. nachfolgendem weiteren Text eingegeben wurde, soll dies nun natürlich ebenfalls eingelesen werden, daher wird der Rest mit scanner.nextLine() komplett konsumiert.
Das vorherige scanner.next() ist nötig, da scanner.nextLine() seinerseits NICHT auf Eingabe wartet, wenn keine da ist und in diesem Fall eine Excetption (lernen wir später) auslösen würde.
13 Hier wird geschnuppert, ob eine gültige Integer-Zahl vorliegt. Wenn gar kein Token vorhanden ist, wird gewartet, und erst bei Vorhandensein geprüft.
14 Nun wird die nun gesichert vorliegende Integer-Zahl konsumiert
15 Wieder wird Müll (also Nicht-Integer Tokens) "entsorgt".

4.4. Konfiguration des Scanners

Folgende Aspekte sind konfigurierbar:

  • zu verwendender Zeichensatz (UTF-16, UTF-8, Cp1252 == windows-1252 ~= ISO 8859-1, …​): im Konstruktor (charset)

  • Locale (u.a. Komma-Zeichen, 1000er-Trenner, …​): useLocale(..)

  • Token-Trenn-Muster (Delimiter, Standard: Java-Whitespaces …​ Space, Tab, Newline etc.): useDelimiter(..)

  • Default-Zahlen-Basis für nextInt(), nextFloat(), etc.: useRadix(..)

Details siehe Javadoc: Scanner (Java SE 17 & JDK 17)