Java-Basics

(Version: 17. April 2023)

1. Einführung

1.1 GIT-Repository als ePortfolio

GIT, Markdown und die entsprechende Repository-Erstellung auf Gitlab oder Github haben sie ja bereits kennen gelernt. Richten sie mit ihrem Schulaccount für diesen Kurs ein Java-Repository in Gitlab ein, geben sie dem Dozenten darauf Lese- und Schreibrechte. Sämtliche hier folgenden Arbeiten sollen auf dieses Repository geladen werden. Vergessen wie Git funktioniert? Hier die Auffrischung: M300 Einführung GIT und Setup des M300 Repositorys und Persönliches Portfolio mit GIT und Markdown schlussendlich auch hier GIT in M319

1.2 Was ist Java?

Der Computer bzw. der darin werkelnde Prozessor versteht grundsätzlich nur den für Meschen kaum lesbare, binäre Maschinencode. Im frühen Computerzeitalter wurde tatsächlich mit der Assembler-Sprache sehr maschinennah programmiert. Heutzutags verwendet man aber hauptsächlich höhere Programmiersprachen wie wie z.B. C++, wo sich ein Sourcecode als ASCII-Text dank an die Mathematik angelehntem Wortschatz und Grammatik gut lesen lässt. Damit's die Maschine auch versteht, muss der Programmtext (Sourcecode) in Maschinencode übersetzt werden. Das erledigt der Kompiler. Der Wermutstropfen dabei ist, dass ein Kompiler nur immer eine Prozessorarchitektur bedienen kann. Möchte man seine Applikation auf verschiedenen Systemen verwenden, muss für jede Plattform mit dem entsprechenden Kompiler kompiliert werden. Aber es gibt auch Ausnahmen. Einige Programmiersprachen müssen nicht sofort in Maschinencode übersetzt werden. Dies geschieht erst bei der Verwendung in einem Wirtprogramm. Dieses auf die entsprechende Plattform zugeschnittene Wirtprogramm kompiliert den Sourcecode quasi zur Laufzeit in Maschinencode. Allerdings spricht man dann in der Fachwelt nicht von «kompilieren» sondern vielmehr von «interpretieren». Javascript (nicht zu verwechseln mit Java!) ist eine solche Sprache. Das Wirtsprogramm ist dabei der Webbrowser.
Ein Mittelweg beschreitet Java. Der Sourcecode wird in einen Zwischencode, den Bytecode, kompiliert. Danach übersetzt die plattformspezifische Java-Laufzeitumgebung den Bytecode in Maschinencode.

Wichtige Begriffe

  • IDE: Integrated Development Environment oder Integrierte Entwicklungsumgebung.
  • SDK: Software Development Kit ist eine Sammlung von Programmierwerkzeugen und Programmbibliotheken. (wie z.B. JDK)
  • JRE: Java Runtime Environment oder Java-Laufzeitumgebung
  • JVM: Java Virtual Machine ist die Schnittstelle zur Maschine und zum Betriebssystem. (JVM ist Teil der JRE)
  • JDK: Java Software Development Kit. JDK ist ein SDK. (Java-Compiler javac.exe, Java-Debugger und JVM sind Bestandteile der JDK)
    (Wollen sie nur Java-Programme ausführen, benötigen sie nur die, meist vorinstallierte, JVM. Wollen sie hingegen auch Java programmieren, greifen sie zum JDK.)
  • JAR: Java-Archiv, Java-Klassenbibliotheken. Mit «java -jar xxx.jar» könnte man sogar JAR-Dateien aus der Kommandozeile starten.
    Wenn ein JAVA-Code fertig erstellt ist, kann man die Applikation in eine JAR-Datei packen. Java-Archive werden häufig verwendet, um Applikationen auf einem Produktionsserver bereitzustellen. Eine Build-JAR-Datei wird als Java-Artifact bezeichnet.
  • Java-Artifact: Ein Java-Artefakt ist eine Datei, normalerweise eine JAR-Datei, die in einem Maven-Repository bereitgestellt wird. Ein Maven-Build erzeugt ein oder mehrere Artefakte, zB. eine kompilierte JAR-Datei und eine Quellen-JAR-Datei. Jedes Artefakt hat eine Gruppen-ID (normalerweise ein umgekehrter Domänenname, wie ch.example.helloworld), eine Artefakt-ID (Name) und einen Versions-String.
  • Maven: Apache-Maven ist ein in der Programmiersprache Java geschriebenes Projektmanagement-Werkzeug. Maven ist in Java-IDE's wie z.B. IntelliJ bereits enthalten. Ein wesentliches Merkmal von Maven-Projekten ist eine einheitliche Verzeichnisstruktur.

Programmierparadigma «Prozedural» versus «Objektorientiert»

Der Begriff «Paradigma» beschreibt eine grundsätzliche Denkweise.
Bei der Computerprogrammierung herrschte der prozedurale Ansatz, vertreten durch z.B. Fortran, COBOL, C, PASCAL, bis in die Neunzigerjahre vor. Dieser entstand kurz nach den Anfängen der Computertechnik, als man sich mit den ersten Hochsprachen von der hardwarenahen Assemblerprogrammierung emanzipierte. Allmählich wurde dieser aber vom objektorientierten Programmierparadigma mit Sprachen wie z.B. C++ und Java abgelöst. Während beim prozeduralen Ansatz der Programmcode dem späteren Ablauf auf Hardwareebene ähnlich ist und diesen grundsätzlich festlegt, abstrahiert der objektorientierte Ansatz. Somit wird die prozedurale Programmierung als eine Abstraktionsstufe zwischen Assembler und objektorientierter Programmierung betrachtet. Was ist beiden Paradigmen gemeinsam?

  • Eine Aufgabe wird in Teilaufgaben, sogenante Prozeduren und Funktionen aufgeteilt.
  • Imperatives Programmieren wird unterstützt. Dass heisst, das ein Programm aus einer Folge von Anweisungen besteht, die vorgeben, in welcher Reihenfolge was vom Computer getan werden soll.
  • Strukturierte Programmierung: Beinhaltet die Zerlegung eines Programms in Teilprogramme und die Beschränkung auf die Kontrollstrukturen Sequenz, Abstraktion, Selektion und Iteration. Sprungbefehle wie GOTO gehören da nicht dazu. Streng genommen continue und break auch nicht.
Und wo liegen die Unterschiede?
  • Beziehung zwischen Daten und Funktionen:
    Objektorientierten Programmierung → Daten (Attribute, Eigenschaften) und Funktionen (Methoden) auf Daten werden in Objekten zusammengefasst.
    Prozeduralen Programmierung → Daten und Funktionen haben keinen Zusammenhalt.
  • Der objektorientierte Ansatz ermöglich Vererbung und Polymorphie:
    Vererbung: Eine von einer Basisklasse abgeleitete Klasse besitzt (erbt) Methoden und Attribute der Basisklasse.
    Polymorphie: Tritt in Zusammenhang mit Vererbung auf. Eine Methode ist polymorph, wenn sie in verschiedenen Klassen die gleiche Signatur hat, jedoch erneut implementiert ist.

1.3 Java-Installation

  1. Laden Sie den Java-Compiler von der Oracle-Webseite herunter: Java-Download
  2. Führen sie das Installationsprogramm aus. Die Installation findet man unter C:\Programme\Java\jdk-x.x.x. Den Compiler im Unterverzeichnis .\bin (javac.exe)
  3. Setzen sie den Java-Pfadnamen, damit die SW pfadunabängig genutzt werden kann:
    • Geben sie in das WIN-Suchfeld den Begriff «Umgebungsvariablen» ein und wählen sie «Systemumgebungsvariablen bearbeiten».
    • Wählen sie «Umgebungsvariablen» → «Systemvariablen» und darin «Path» → Bearbeiten.
    • Tragen sie als letzte Zeile den Pfad zum Java-Bin-Verzeichnis ein → C:\Programme\Java\jdk-x.x.x\bin (x.x.x ist die aktuelle Java-Version!)
    • Als Vorbereitung zum Arbeiten mit einer IDE tragen wir bereits jetzt auch die Java-Home-Environment-Variable ein:
      Wähle «Neu...» Es erscheint ein Fenster «Neue Benutzervariable
    • Tragen sie dort ein: Name der Variablen = JAVA_HOME (Alles Grossbuchstaben!) und bei Wert der Variablen = C:\Programme\Java\jdk-x.x.x\bin (x.x.x ist die aktuelle Java-Version!)
    • Nebenbei: Sie können diese Einträge anstatt systemweit auch nur für sie einrichten.
  4. In der Konsole (cmd) kann die Installation überprüft werden: Konsoleneingabe: javac
    Ist Java korrekt installiert, so erscheint eine Meldung in der Form:
    Usage : javac ... where possible options include: ...
    Sollte Java nicht korrekt installiert sein, ein Fehlermeldung wie...
    Der Befehl "javac" ist entweder falsch geschrieben oder konnte nicht gefunden werden. (z.B. fehlende Umgebungsvariable für Java)
Falls Java nicht korrekt installiert ist, kann folgendes weiterhelfen:
  • Computerneustart nach der Installation von Java.
  • Aufsuchen des Programms javac und den gefundenen Pfad in die Systemvariable %PATH% entsprechend ergänzen. Danach Neustart der Konsole.
  • Neuinstallation des JDK (Wurde wirklich auch das JDK und nicht nur die JVM installiert?).

1.4 Programmentwurf

Wir verfolgen den Ansatz des strukturierten Programmentwurfs: «Analyse → Design → Implementation → Test» bzw. «IPERKA»
Dabei setzen wir «Unified Modeling Language → Activity-Diagram» oder als Alternative «Nassi-Shneidermann» ein. Als Zeichenprogramm verwenden wir für Aktivitätsdiagramme die Online-Applikation Draw.io und für Nassi-Shneidermann-Diagramme Structorizer ein. Weitere Informationen finden sie auf dieser Webseite im Beitrag zu Programmentwurf.
Jedem hier erstellten Programmcode geht ein Programmentwurf voraus. Dieser soll als JPG-Bild ebenfalls ins GIT-Repository hochgeladen werden.

1.5 Erste Schritte in der Java-Programmierung

Dieser Einstieg erfolgt bewusst mit einem Texteditor und der Kompilierung in der Konsole. Damit kann z.B. auch die Java-Installation verifiziert werden. Später werden wir selbstverständlich mit einer IDE (IntelliJ) arbeiten.
Erstellen sie nun mit einem ASCII-Texteditor ihrer Wahl (Empfohlen wird Notepad++) folgende Zeilen:

import java.io.*;
public class Main
{

  public static void main(String[] args)
  {
    System.out.println("Guten Tag, ihr erstes Java-Programm.");
  }
}
und speicher sie es als Main.java ab.
Öffnen sie die WIN-Konsole (cmd) und navigieren sie an den Speicherort von Main.java.
Kompilieren sie den Sourcecode Main.java mit dem Java-Compiler: javac Main.java
Sie erhalten eine Datei Main.class, die sie nun mit der Java-virtuellen Maschine wie folgt ausführen können: java Main
In der Konsole erscheint die Meldung
"Guten Tag, ihr erstes Java-Programm."

Und nun gleich noch dies:
import java.io.*;
import java.time.LocalTime;
import java.time.ZoneId;

public class Main
{

  public static void main(String[] args)
  {
    String myVar1="zweites";
    ZoneId meineZeitzone=ZoneId.systemDefault();
    LocalTime time = LocalTime.now(meineZeitzone);

    if(time.getHour()<=12)
      System.out.print("Guten Morgen, ihr " + myVar1 + " Java-Programm.\n");
    else
      System.out.print("Guten Nachmittag, ihr " + myVar1 + " Java-Programm.\n");
  }
}
und speicher sie es als Main.java ab. Die Kompilierung und Programmausführung erfolgt im gleichen Sinne wie beim ersten Programm. In der Konsole erscheint nun je nach Uhrzeit die Meldung
"Guten Morgen, ihr zweites Java-Programm." oder
"Guten Nachmittag, ihr zweites Java-Programm."

Praxisaufgabe ∇ AUFGABE
∇ LÖSUNG

1.6 Installation einer IDE und SDK

So wie sie soeben ihr erstes Java-Programm erstellt und ausgeführt haben war eher umständlich. Komfortabler geht es mit einer IDE. IDE ist SW und steht für Integrated Development Environment oder Integrierte Entwicklungsumgebung. Es sind verschiedene Java-IDEs erhältlich wie z.B. VisualStudio, VSCode, NetBeans, Eclipse, IntellijIdea und weitere.
Da unter Java-Programmierer Intellij-Idea aber am weitesten verbreitet ist, werden auch wir dieses einsetzen. Der Link zur Software finden sie hier: Intellij-Idea (von Jetbrains)


2. Java Grundkenntnisse

Jetzt geht's endlich los - Viel Erfolg!

Grundsätzlich besteht ein Java-Programm aus einer main-Funktion. Damit ist das Hauptprogramm gemeint. Da Java eine objektbasierte Sprache ist, muss die main-Funktion in eine Klasse eingefügt werden. Nun spricht man nicht mehr von der main-Funktion, sondern von der main-Methode, weil Funktionen eines Objektes eben Methoden genannt werden.
Klassen benennt man nach der PascalNamingConvention, darum «class Main»
Methoden benennt man nach der camelNamingConvention, darum «void main»
Man beachte auch die geschweiften Klammern { } die BEGIN-OF-BLOCK →{ und END-OF-BLOCK →} bedeuten. Ausserdem wird jede Anweisung zwecks Eindeutigkeit mit einem Semikolon ; abgeschlossen.

public class Main {                         //Main-Class

  public static void main(String[] args) {  //Main-Method
    //Hier steht der Java-Code
  }

}

2.1 Programmheader und Kommentare

Und schon die erste Spassbremse: Ihre Programme benötigen zwingend einen Programmheader und im Verlauf des weiteren Codes eine hinreichende Anzahl von erklärenden Kommentaren. Das hilft einerseits ihnen, sich nach «Ferien, in denen sie alles vergessen» in ihrem Code wieder zurecht zu finden, und anderenseits beim Neueintritt in ihre Traum-Firma, den Code des Arbeitskollegen/in überhaupt zu verstehen.

  • Einzeiliger Kommentar: Startet mit // und führt bis Zeilenende.
  • Blockkommentar: Startet mit /* und endet mit */
  • Dokumentationskommentar: Startet mit /** und endet mit */
(BTW: Wenn sie im Programmheader gewisse Angaben mit einem @ versehen (wie z.B. @author FelixMuster, @version 1.1, etc.) kann man mit Hilfe von javadoc eine HTML-Dokumentation generieren.)

Kommentar-Beispiel:
import java.util.Scanner;  //Java-Klasse zum Einlesen von Werten aus der Kommandozeile
/**
 * Diese Programm begrüsst sie freundlich und
 * fragt sie nach ihrer Java-Wunschnote
 * @author   Felix Muster
 * @version  1.0
 * @see      java.util.Scanner
 */
public class Main
{
  public static void main(String[] args)
  {
    //Deklaration der beiden Variablen
    String myname;            //Textvariable für die Namensabfrage als 
    float DesiredSchoolGrade; //Fliesskommazahlvariable für die Abfrage der Wunschnote
    
    Scanner scanner = new Scanner(System.in);          //Installieren eines InputStreamReaders
    System.out.print("Gebe bitte deinen Namen ein: "); //Text für die Engabeaufforderung
    myname = scanner.nextLine();                       //Eingegebene Zeile der Variablen zuweisen

    System.out.print("Hallo " + myname + ", was ist deine Wunschnote in Java-Programmierung? ");
		
    /*Hier folgt der Begrüssungstext und die
      und erneute Abfrage nach der Wunschnote*/
    DesiredSchoolGrade=scanner.nextFloat();
    System.out.println("--------------------------------------");
    System.out.println("OK, habe verstanden!");
    System.out.println("Sehr ehrgeizig, deine Wunschnote " + DesiredSchoolGrade);
    System.out.println("Viel Erfolg dabei!");
  }
}

2.2 Sourcecode-Formatierung

Und da noch die zweite Spassbremse: Damit ihr Sourcecode gut lesbar ist, werden ein paar verbindliche Abmachungen getroffen:

  • Verbundanweisungen werden zwei Zeichen nach rechts eingerückt.
  • Variablennamen sollen selbstsprechend sein. (z.B. int anzLoops = 0; boolean nOK = true; etc.)
  • Programme und Funktionen werden mit Programmheader bzw. Dokumentationskommentar versehen (Zweck, Input, Output)
  • Blockkommentare sind Überschriften zum Algorithmus.
  • Spezifischer einzeiliger Kommentar ist die Antwort auf «Wozu dient diese Programmzeile?»
Hinweis zur IDE IntelliJ: Dort gibt es verschiedene Hilfsmittel zur Üterstützung einer sauberen Sourcecode-Formatierung:
Menüpunkt Code: Hier kann der Sourcecode analysiert, aufgeräumt oder ergänzt werden.
Menüpunkt Refactor: Hier kann der Sourcecode umstrukturiert, Überarbeitet oder umgestaltet werden.

2.3 Variablen und Datentypen

Ganz wichtig in einer Programmiersprache sind die Variablen. Diese kennen wir ja bereits aus der Mathematik, wie folgendes Beispiel zeigt:

            x    =    3
            y    =    2 * x + 1
            z    =    sin(x)
LeftsideValue  Equal  RightsideValue

x und y sind Variablen, die mit einem Zahlenwert befüllt werden.
Die Zahlen 1,2 und 3 sind die Konstanten (Literale).
* und + sind Operatoren.
sin() ist ein Funktionsaufruf der Funktion Sinus (Trigonometrie).
= ist der Zuweisungsoperator.

Es wird immer zuerst der RightsideValue evaluiert bzw. berechnet und danach das Resultat
dem LeftsideValue zugewiesen.
Da der Rechner Daten ab- oder Zwischenspeichern muss, benötigt er eine Angabe zu der dafür verlangten Speichergrösse. Darum muss man zuerst festlegen, in welchem Umfang eine Variable Speicher benötigt. Und da kommen die Datentypen ins Spiel.

Primitive Datentypen

DATENTYP Beschreibung Java-Syntax Grösse in Bit Wertebereich
Boolean Logisch Wahr Falsch boolean myVar1 = true; undefiniert true, false
Character Ein UTF-16 Zeichen char myVar2 = 'A'; 16 0 ... 65'535
Byte 2er-Komplement-Wert byte myVar3 = 10; 8 -128 ... +127
Short 2er-Komplement-Wert short myVar4 = -6; 16 -32'768 ... +32'767
Integer 2er-Komplement-Wert int myVar5 = 1; 32 -2'147'483'648 ... +2'147'483'647
Long 2er-Komplement-Wert long myVar6 = 20; 64 -263 ... +263-1
Float Fliesskommazahl
Einfache Genauigkeit
float myVar7 = 1.00f; 32 ±1.4E-45 ... ±3.4E+38
Double Fliesskommazahl
Doppelte Genauigkeit
double myVar8 = 3.14; 64 ±4.9E-324 ... ±1.7E+308
(Unterschied UTF-8 zu UTF-16: UTF-8 ist abwärtskompatibel zu 7-Bit-ASCII. UTF-16 ist immer zwei Byte lang.)

Variablen deklarieren und initialisieren

Im Gegensatz zu vielen Skriptsrachen wie z.B. Powershell, müssen in Java oder auch C Variablen explizit deklariert werden. Damit meint man die vom Programmierer bewusst ausgelöste Direktive an das Betriebssystem, Speicher im RAM zu reservieren. Die Angabe des Datentyps ist darum nötig, weil das Betriebssystem wissen muss, wieviele Bits es bereitzustellen hat. Für ein Integer z.B. 32 Bit, für ein Character (UTF-16) 16 Bit und beim String 16Bit pro einzelnes Zeichen im String. (Bei String als Objekt sieht es etwas anders aus.) Die Verwaltung des Primärspeichers kann man sich wie ein Bootsverleih vorstellen. Boote werden vergeben, von Kunden benutzt, zurückgegeben, der Abfall entsorgt und dann neu vermietet. Im Gegensatz zum Bootsvermieter, der die eingehenden Boote reinigt, bevor er sie neu vermietet, verzichtet das Betriebssystem aus Performancegründen darauf. Ihm ist es also egal, was im zurückgegebenen Speicher hinterlassen wurde. Ich als Speicherkunde des Betriebssystems erhalte so "ungereinigten" Speicher. Das ist normalerweise auch kein Problem, weil ich die Variable ja neu befülle und verwende. Wo gearbeitet wird, passieren bekanntlich Fehler. Schleicht sich beim Abfüllprozss meiner Variablen ein semantischer Fehler ein, wird mein Programm zicken und Debugging ist angesagt. Spätestens jetzt würde sich auszahlen, von definierten Initial-Zustände meiner Variablen auszugehen. D.h. meine 32 angeforderten Bits für meine Variable myVar sind alle auf logisch 0 und repräsentierten damit den Dezimalwert 0. Dafür muss ich aber selber sorgen. Und exakt das nennt man «Initialisierung von Variablen».
(Hinweis: Einige Compiler übernehmen diese Aufgabe für sie, aber leider nicht alle. Darum initialisieren sie, schaden kanns nicht.)
Beispiele:

bool   myBool   = false;
int    myInt    = 0;
char   myChar   = ' ';
float  myFloat  = 0.0;
string myString = " ";

Der Datentyp String

Der Datentyp String ist kein primitiver Datentyp. Da ist die Sache etwas komplizierter. Grundsätzlich ist ein String ein Array of Character. In Java sogar eine Klasse. (D.h.: Eine Java-Stringvariable enthält nicht den String selbst, sondern einen Verweis auf ein Objekt der Java-Klasse String.)

String myVar1;        //Variablendeklaration
myVar1="Hallo Welt!"  //Wertzuweisung

//Mit folgender Zeile wird implizit ein String-Objekt erzeugt:
myVar1="Hallo Welt!"
//Explizit sähe das so aus: 
myVar1=new String"Hallo Welt!"
Vorsicht beim Vergleich von zwei Strings:
//Folgendes führt nicht zu dem, was man vielleicht erwarten würde:
import java.util.Scanner;
public class Main
{
    public static void main(String[] args)
    {
        String myVar1="Hans";
        String myVar2="Hans";
        if(myVar1 == myVar2)
          System.out.print("Namen sind identisch!"); 
    }
}
//Man vergleicht damit, ob sich zwei Objekte an der gleichen Speicherstelle befinden. 
//Will man die Inhalte vergleichen, muss die String-Objekt-Methode equals()
//oder compareTo() verwendet werden, wie folgendes Beispiel zeigt:

import java.util.Scanner;
public class Main
{
    public static void main(String[] args)
    {
        String myVar1=new String("Hans");
        String myVar2=new String("Hans");
        if(myVar1.equals(myVar2))                    //Variante A
          System.out.print("Namen sind identisch!");
        if((myVar1.compareTo(myVar2)) == 0)          //Variante B
          System.out.print("Namen sind identisch!");
    }
}
Sämtliche Variablen sind unmittelbar nach dem Programmheader zu DEKLARIEREN und gleichzeitig zu INITIALISIEREN.
Begründung: Eine explizite Variablendeklaration in einer eigens dafür eingerichteten Sektion hilft, die Übersicht zu behalten. Mit der Initialisierung einer Variablen mit einem Initialwert stellt man sicher, dass ein Anfangswert gesetzt ist.
(Mit der Variablendeklaration fordert man Speicher an, der durch das Betriebssystem verwaltet wird. Es führt die Reservationsliste und weiss somit, wo freier Speicher verfügbar ist. Das Betriebssystem übernimmt aber nicht die Säuberung des zurückgegebenen Speichers, es markiert ihn nur als frei und somit verfügbar. Der Nachfolger übernimmt also den Speicherzustand so wie er ist bzw. wie er zuvor abgegeben wurde. Darum ist man gut beraten, den Speicher innerhalb seines Programms auf einen Initialwert zu setzen. Somit lässt sich vielleicht das eine oder andere nicht nachvollziehbare Verhalten des Programms beim Debuggung verhinden.)

Konstanten:

Hin und wieder verwendet man Werte, die sich nicht verändern, möchte die aber einmal definieren und später nur noch über einen Namen ansprechen. Ein Beispiel aus der Mathematik wäre die Konstante π mit 3.141562. Hier ist eine unveränderbare Vaiable, wie die Konstante auch noch genannt wird, sinnvoll. Es gilt die Abmachung, dass man Namen von Konstanten mit Grossbuchstaben schreibt. Und so wird sie z.B. deklariert:
final float CONST_PI = 3.141562f;

Aufzählungstyp:

Eine Aufzählung (Enumeration oder kurz enum) ist eine spezielle Klasse, die eine Gruppe von Konstanten darstellt.

enum RGBfarbe
{
  ROT,
  GRUEN,
  BLAU
}

RGBfarbe meineFarbe = RGBfarbe.ROT;

switch(meineFarbe)  //Ein enum-Anwendungsbeispiel
{
  case ROT:   System.out.println("Rot");
              break;
  case GRUEN: System.out.println("Gruen");
              break;
  case BLAU:  System.out.println("Blau");
              break;
  default:    System.out.println("Schwarz");
              break;
}

2.4 Die Feldvariable/Array

Feldvariablen (Arrays) werden verwendet, um mehrere Werte in einer einzigen Variablen zu speichern, anstatt separate Variablen für jeden Wert zu deklarieren. Es gibt eindimensionale, aber auch mehrdimensionale Arrays. Ein Array-of-String ist eigentlich ein zweidimensionales Array, weil der String selber schon ein eindimensionales Array-of-Character darstellt.

Beispiel mit Integer (Eindimensional):
int i = 0;  //Laufvariable für die for-Schleife
int[] myArrayA = {1, 2, 3, 4};  //Array mit 4 Elementen deklarieren und implizit initialisieren
myArrayA[0] = 1;  //Erstes bzw.  0-Element des Arrays mit 1 beschreiben
myArrayA[1] = 2;  //Zweites bzw. 1-Element des Arrays mit 2 beschreiben
myArrayA[2] = 3;  //Drittes bzw. 2-Element des Arrays mit 3 beschreiben
myArrayA[3] = 4;  //Drittes bzw. 2-Element des Arrays mit 4 beschreiben
myArrayA[4] = 5;  //Führt wegen Array-Bereichsüberschreitung zu einem Fehler!
System.out.println("myArrayA-Element-0 = " + myArrayA[0]);
for(i=0; i<=4; i++)
  System.out.print(myArrayA[i] + " ");
  
//Alternative Variante
int myArrayB[] = new int[4] //Array-Objekt mit 4 Elementen deklarieren
myArrayB[0] = 1;  //Erstes bzw.  0-Element des Arrays mit 1 beschreiben
myArrayB[1] = 2;  //Zweites bzw. 1-Element des Arrays mit 2 beschreiben
myArrayB[2] = 3;  //Drittes bzw. 2-Element des Arrays mit 3 beschreiben
myArrayB[3] = 4;  //Drittes bzw. 2-Element des Arrays mit 4 beschreiben
myArrayB[4] = 5;  //Führt wegen Array-Bereichsüberschreitung zu einem Fehler!
System.out.println("myArrayB-Element-0 = " + myArrayB[0]);
for(i=0; i<=4; i++)
  System.out.print(myArrayB[i] + " ");
  
if(myArrayA[3] == myArrayB[3])
  System.out.print("Inhalt ist identisch");

//Arraylänge ermitteln
System.out.println(myArrayA.length);  //myArrayA.length ist Eigenschaft/Property des Array-Objekts myArrayA
System.out.println(myArrayB.length);  //myArrayB.length ist Eigenschaft/Property des Array-Objekts myArrayB

Beispiel mit Integer (Zweidimensional):
int x = 0;  //Laufvariable für die for-Schleife
int y = 0;  //Laufvariable für die for-Schleife
int[][] myArrayC = {{0, 0, 3, 4, 5}, {6, 7, 8, 9, 0}};  //Array deklarieren und inplizit initialisieren
myArrayC[0][0] = 1; //Element des Arrays überschreiben
myArrayC[0][1] = 2; //Element des Arrays überschreiben
System.out.println(myArrayC[0][1]);
for(y=0; y<=1; y++)
{
  for(x=0; x<=4; x++)
    System.out.print(myArrayC[y][x] + " ");
  System.out.println(" ");
}

Beispiel mit String:
int i = 0;  //Laufvariable für die for-Schleife
String[] Wochentag = {"Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag","Sonntag"};
for(i=0; i<=7; i++)
  System.out.println(Wochentag[i]);

2.5 Die Operatoren

Mathematische Operatoren:

  • Addition → +
  • Subtraktion (auch Vorzeichen) → -
  • Multiplikation → *
  • Division → /
  • Restbildung Modulo → %
  • Inkrement +1 → ++
  • Dekrement -1 → --
  • Bsp1.: myVar3 = myVar1 + myVar2;
  • Bsp2.: myVar2 = ++myVar1; (Achtung: Preincrement ++(..) und Postincrement (..)++ haben verschiedene Auswirkungen.)

Vergleichsoperatoren:

  • Identisch → (myVar1 == myVar2)
  • Ungleich ≠ → (myVar1 != myVar2)
  • Grösser als > → (myVar1 > myVar2)
  • Grösser-gleich als ≥ → (myVar1 >= myVar2)
  • Kleiner als < → (myVar1 < myVar2)
  • Kleiner-gleich als ≤ → (myVar1 <= myVar2)

Logische Operatoren:

  • UND/AND && → ((myVar1 ≥ 0) && (myVar1 ≤ 9))
  • ODER/OR || → ((myVar1 < 100) || (programmabbruch == true)) Alternative: ((myVar1 < 100) || programmabbruch)
  • Logische Umkehrung NOT ! → !programmabbruch
  • 2.6 Die Ein- und Ausgabe über die Konsole

    EINGABE:
    import java.util.Scanner; //Benötigte Java-Klasse
    
    String myString = " ";   //Textvariable
    char   myChar   = ' ';   //Zeichenvariable
    float  myFloat  = 0.0;   //Fliesskommazahl
    int    myInt    = 0;     //Ganzzahl Integer
    
    Scanner myScanner = new Scanner(System.in);  //Installieren eines InputStreamReaders
    myString = myScanner.nextLine();             //Textzeile einlesen
    myChar   = scanner.next().charAt(0);         //Einzelne4s Zeichen einlesen
    myFloat  = myScanner.nextFloat();            //Fliesskommazahl einlesen
    myInt    = myScanner.nextInt();              //Integerzahl einlesen
    
    AUSGABE:
    System.out.print("Hallo Welt!");             //Ausgabe ohne Newline → Prompt steht hinter dem Ausgabetext
    System.out.print("Hallo Welt!\n");           //Ausgabe mit Spezialzeichen \n (=Newline) → Prompt steht auf neuer Zeile
    System.out.println("Hallo Welt!");           //Ausgabe mit println (=Newline) → Prompt steht auf neuer Zeile
    System.out.println(myString);                //Ausgabe der Textvariablen
    System.out.println(myChar);                  //Ausgabe des Zeichens
    System.out.println(myFloat);                 //Ausgabe des Floats
    System.out.println(myInt);                   //Ausgabe des Integers
    System.out.println("Der Text lautet: " + myString);              //Zusammengesetzte Ausgabe
    System.out.print("Das Jahr " + myInt + " ist ein Schaltjahr.");  //Zusammengesetzte Ausgabe
    

    2.7 Die Sequenz

    Die Sequenz ist eine Anweisung bzw. Statement. Beispiele:

    myVar3 = myVar1 + myVar2;
    System.out.print("Hallo Welt!");
    

    2.8 Die Selektion/Mehrfachselektion

    Die Selektion nennt man auch Verzweigung.

    Einseitige Selektion:

    int myVar1 = 10;
    int myVar2 = 20;
    if(myVar2 > myVar1)
    {
      System.out.println("myVar2 > myVar1");
      //Es folgt weiterer Code
      //Man nennt dies Verbundanweisung
    }
    
    if(myVar2 > myVar1)
      System.out.println("myVar2 > myVar1"); //Nur eine Sequenz darum keine {}-Klammern nötig!
    

    Zweiseitige Selektion:

    int myVar1 = 10;
    int myVar2 = 20;
    if(myVar2 > myVar1)
    {
      System.out.println("myVar2 > myVar1");
      //Es folgt weiterer Code
      //Man nennt dies Verbundanweisung
    }
    else
    {
      System.out.println("myVar2 <= myVar1");
      //Es folgt weiterer Code
      //Man nennt dies Verbundanweisung
    }
    
    if(myVar2 > myVar1)
      System.out.println("myVar2 > myVar1");   //Nur eine Sequenz darum keine {}
    else
      System.out.println("myVar2 <= myVar1");  //Nur eine Sequenz darum keine {}
    

    Mehrfachselektion:

      int myVar1 = 2;
      switch(myVar1)
      {
        case 1:  System.out.println("Eins");
                 break;
        case 2:  System.out.println("Zwei");
                 break;
        case 3:  System.out.println("Drei");
                 break;
        default: System.out.println("Sonst");
                 break;
      }
      
        case >3: Wäre nicht zulässig! 
    
    
      if(myVar1==1)
        System.out.println("Eins");
      else
        if(myVar1==2)
          System.out.println("Zwei");
        else
          if(myVar1==3)
            System.out.println("Drei");
          else
            System.out.println("Sonst");
    

    2.9 Die Iteration

    Die Iteration nennt man auch Schleife.

    Kopfgesteuerte Iteration:

      int myVar1 = 0;
      while(myVar < 10)
      {
        System.out.println(myVar1 + ". Durchgang");
    	myVar=myVar+1;
      }
      
      //Die Schleife wird hier ausgelassen: 
      int myVar1 = 15;
      while(myVar < 10)
      {
        System.out.println(myVar1 + ". Durchgang");
    	myVar=myVar+1;
      }
    
      //Alternative, die die Syntax vereinfacht:
      for(myVar1=0; myVar1 < 10; myVar=myVar+1)
      {
        System.out.println(myVar1 + ". Durchgang");
      }
    

    Fussgesteuerte Iteration:

      int myVar1 = 0;
      do
      {
        System.out.println(myVar1 + ". Durchgang");
        myVar=myVar+1;
      }while(myVar1 < 10);
     
      //Achtung: Da wird die Schleife trotzdem einmal durchlaufen: 
      int myVar1 = 15;
      do
      {
        System.out.println(myVar1 + ". Durchgang");
        myVar=myVar+1;
      }while(myVar1 < 10);  
    



    Praxisaufgabe ∇ AUFGABEN
    ∇ LÖSUNGEN

    3. Java-Reloaded

    3.1 Der Typecast

    Um das Typenkonzept nicht zu verletzen, muss bei bestimmten Situationen eine Typenumwandlung (Typecast) erfolgen. Unter dem Typenkonzept versteht man u.a., dass der Datentyp des LeftSideValues (Links vom Zuweisungsoperator) dem Typ des RightSideValues (Rechts vom Zuweisungsoperator) entsprechen muss. Schliesslich kann ich ja nicht einen 64Bit-Wert (RightSideValue) in eine 32Bit-Variable (LeftSideValue) reinstopfen.
    Wo kommt ein Typecast zum Beispiel zur Anwendung? Dividieren siezwei Intergerzahlen ist die Wahrscheinlichkeit hoch, dass eine Dezimalzahl mit Nachkommastellen resultiert. Gemäss Typenkonzept müsste dieses Resultat einer Integer-Variablen zugewiesen werden. Und da kommt der Typecast zum Einsatz. Das Resultat muss in einen Float gewandelt werden und kann dann einer Float-Variablen zugewiesen werden.

    • Widening Casting (Automatisch/Implizit): byte → short → char → int → long → float → double
    • Narrowing Casting (Manuell/Explizit): double → float → long → int → char → short → byte
      Es kann Genauigkeit verloren gehen!
    int myVar1 = 9;
    double myVar2 = myVar1;      //Automatischer, impliziter Typecast
    System.out.println(myVar1);  //Ausgabe → 9
    System.out.println(myVar2);  //Ausgabe → 9.0
    
    double myVar3 = 9.816;
    int myVar4 = (int) myVar3;   //Manueller, explizit Typecast von double zu int
    System.out.println(myVar3);  //Ausgabe → 9.816
    System.out.println(myVar4);  //Ausgabe → 9 (Es wird nicht gerundet, nur "abgeschnitten"
    

    3.2 Try and Catch

    Beim Ausführen von Java-Code können verschiedene Fehler auftreten wie z.B. Codierungsfehler, falsche Eingaben zur Laufzeit oder andere unvorhersehbare Dinge. Wenn ein Fehler auftritt, stoppt Java normalerweise und generiert eine Fehlermeldung. Der Fachbegriff dafür lautet: Java will throw an error (throw an error). Da kommt Try and Catch ins Spiel: Mit der try-Anweisung definiert man einen Codeblock, der während der Ausführung auf Fehler getestet werden soll. Im catch-Anweisungsteil definiert man, was im Fehlerfall dann ausgeführt werden soll.
    Beispiel:

    import java.util.Scanner;
    public class Main
    {
      public static void main(String[] args)
      {
        String[] Wochentag = {"Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag","Sonntag"};
        int myVar1;
        boolean ok = true;
    
        do
        {
          Scanner myScanner = new Scanner(System.in);
          System.out.print("Welcher Wochentag (1..7) soll ich anzeigen? ");
          myVar1 = myScanner.nextInt();
    
          try
          {
            System.out.println(Wochentag[myVar1-1]);	
          }
          catch(Exception e)
          {
            System.out.print("Den Wochentag " + myVar1 + " gibts nicht!");
            System.out.println(" Sie sind damit out!");
            ok = false;
          }
        } while(ok);
      }
    }
    

    3.3 Konsoleneingabe einer ganzen, positiven Zahl erzwingen

    Soll ein Programm robust sein, muss es gegen falsche Eingaben gesichert werden. Dieses Codebeispiel zeigt auf, wie das gehen könnte, wenn vom Benutzer verlangt wird, eine positive, ganze Zahl einzugeben:

    import java.util.Scanner; //Import für Input/Output
    public class Main
    {
    
      //Funktions zur Prüfung der Eingabe
      public static boolean EingabeIstEineZahl(String string)
      {
        if (string==null || string.length()==0)
        return false;  //Funktion wird vorzeitig verlassen
        for ( int i = 0; i < string.length(); i++)
          if (!Character.isDigit(string.charAt(i)))
            return false;
        return true;
      }
    
      //Hauptprogramm
      //Variablendeklaration und Initialisierung
      public static void main(String[] args)
      {
        enum EingabeStatusVariante  //Aufzählungstyp
        {
          EingabePendent,
          EingabeIstEineZahl,
          EingabeIstKeineZahl,
          ZahlAusserhalbBereich
        }
        EingabeStatusVariante EingabeStatus = EingabeStatusVariante.EingabePendent;
        String meineEingabe="";
        int meineZahl=0;
        Scanner KonsoleneingabeLesen;
        KonsoleneingabeLesen=new Scanner(System.in);  //Installieren eines InputStreamReaders
        //Ginge auch in einer Zeile: Scanner KonsoleneingabeLesen=new Scanner(System.in);
    	
        //Eingabe:
        System.out.print("Bitte eine positive Ganzzahl zwischen 0 und 100 eingeben: ");
        meineEingabe=KonsoleneingabeLesen.nextLine();
    	
        //Verarbeitung:
        if(EingabeIstEineZahl(meineEingabe) != true)   //Alternative: if(!EingabeIstEineZahl(meineEingabe)
          EingabeStatus = EingabeStatusVariante.EingabeIstKeineZahl;
        else
        {
          meineZahl=Integer.parseInt(meineEingabe);
          if((meineZahl >=0) && (meineZahl <=100))
            EingabeStatus = EingabeStatusVariante.EingabeIstEineZahl;
          else
            EingabeStatus = EingabeStatusVariante.ZahlAusserhalbBereich;
        }
    	
        //Ausgabe:
        switch(EingabeStatus)
        {
          case EingabeIstEineZahl:    System.out.println("Ihre Zahl lautet " + meineZahl);
          break;
          case EingabeIstKeineZahl:   System.out.println("Ihre Eingabe " + meineEingabe + " ist keine Zahl!");
          break;
          case ZahlAusserhalbBereich: System.out.println("Ihre Zahl " + meineZahl + " liegt ausserhalb des Bereichs 0..100!");
          break;
          default:                    System.out.println("Fehler!");                   
        }
      }
    }
    

    3.4 Zufallszahlen generieren

    Zufallszahlen kann man in Java mit der Math-Klasse oder der Random-Klasse erzeugen. Die Math-Klasse bietet den Vorteil, dass kein Objekt angelegt werden muss.

    import java.util.Scanner; //Import für Input/Output
    import java.util.Random;  //Import für Random Klasse
    
    public class Main
    {
      public static void main(String[] args)
      {
        int myVar1 = 0;
        int myVar2 = 0;
        double myVar3 = 0.0;
        Random random = new Random(); //Neues Random Objekt
    
        myVar1 = random.nextInt();    //Ganzahlige Zufallszahl
        myVar2 = random.nextInt(10);  //Ganzahlige Zufallszahl zwischen 0..10
        myVar3 = random.nextDouble(); //Zufallszahl
    
        int myVar4 = (int) (Math.random()*10); //Zufallszahl 1..10, INT-Cast, ohne eigenes Objekt. 
    
        System.out.println("1:" + myVar1 + " 2:" + myVar2 + " 3:" + myVar3 + " 4:" + myVar4);
      }
    }
    

    3.5 Unterprogramme, Abstraktion

    Schon bisher sind wir verschiedentlich auf Unterprogramme gestossen, wie zum Beipiel beim System.out.println(). Nun möchten wir solche Unterprogramme aber auch selber erstellen. Was ist aber der Vorteil von Unterprogrammen?

    • Man fasst darin einen immer wiederkehrenden Vorgang zusammen. Man kann seinen Code quasi rezyklieren.
    • Der Gesamtcode wird durch Abstraktionen einfacher lesbar, sofern man die Unterprogramme auch mit sinnvollen und aussagekräftigen Namen versieht.
    • Ein auf Herz und Nieren geprüftes Unterprogramm kann immer wieder und überall bedenkenlos verwendet werden. Auch in anderen Projekten.
    • Ein Unterprogramm hat eine klar definierte Schnittstelle und ermöglicht damit ein HiddenDataConcept. Damit meint man, dass jeder Programmteil nur Zugriff auf die Daten erhält, die es benötigt. Kein irrtümliches Überschreiben von Werten. Stichwort: Globale und lokale Variablen.
    • Unterprogramme ohne Rückgabewerte nennt man Prozeduren.
    • Unterprogramme mit Rückgabewerten nennt man Funktionen.
    • Unterprogramme, die auf Objekten wirken nennt man Methoden.
    Parameterlose Prozedur (ohne Rückgabewert)
    void sayHello() //void steht für Leer
    {
      System.out.println("Hallo!");
    }
    Aufruf: sayHello();
    
    Prozedur mit Parametern (ohne Rückgabewert)
    void zeigeQuadratzahlen(int max)
    {
      int i=0; //Lokale Variable
      for(i=0; i<=max; i++)
        System.out.println("("i: " + (i*i));
    }
    Aufruf: zeigeQuadratzahlen(7);
    
    Hinweis: Der Parameter geht als Wert (7) und nicht als Variable in die Funktion,
    wie folgendes beweist:
    int max=7;
    System.out.println(max);
    zeigeQuadratzahlen(max);
    System.out.println(max);
    Die Variable max im aufrufenden Programm ist trotz gleichem Namen nicht dieselbe,
    wie diejenige in der Funktion!
    
    Funktion mit Parametern (mit Rückgabewert)
    import java.util.Scanner; //Import für Input/Output
    public class Main
    {
      //Die Funktion
      public static long potenzieren(int dieBasis, int derExponent)
      {
        int i=0;              //Lokale Variable
        long derPotenzwert=1; //Lokale Variable
        for(i=0; i < derExponent; i++)
          derPotenzwert=dieBasis*derPotenzwert;
        return derPotenzwert;
      }
      //Das aufrufende Hauptprogramm
      public static void main(String[] args)
      {
        int meineBasis=0;
        int meinExponet=0;
        long meinResultat=0;
        meinResultat=potenzieren(meineBasis,meinExponet);
        System.out.println(meinResultat);
        //Oder gleich so:
        System.out.println(potenzieren(meineBasis,meinExponet));
      }
    }
    

    CallByValue versus CallByReference

    Grundsätzlich werden über Funktionsschnittstellen (Parameter) nicht Variablen, sondern Werte übergeben. Und dies geschieht bei Java, wie auch bei C, als CallByValue. Das heisst, dass der über den Stack in die Funktion übergebene Wert in dieser lokalen Charakter hat und somit Änderungen daran nicht nach aussen wirken. Möchte man einen solchen Effekt trotzdem erreichen, kann man entweder auf die Parameterübergabe verzichten und globale Variablen verwenden, was aber das HiddenDataConcept verletzt, oder man bedient sich des folgenden Tricks: Man übergibt nicht einen Wert, sondern der Speicherort (Adresse) der Variablen mit diesem Wert. Dies ist dann zwar auch CallByValue, man übergibt ebenfalls einen Wert, den Adresswert der Variablen, aber innerhalb der Funktion arbeitet man via Adresse (Referenz) auf der Originalvariablen und Änderungen derselben sind auch ausserhalb der Funktion "wirksam". Dies nennt man CallByReference. Beispiel in Java:

    import java.util.Scanner; //Import für Input/Output
    
    class myIntegerClass  //Klasse für Integer-Objekt
    {
      public int theValue;  //Objekt-Wert, Eigenschaft, Property
      public myIntegerClass(int theValue){ this.theValue = theValue;}  //Konstruktor
    }
    
    public class Main
    {
      public static void main(String[] args)
      {
        int myVar1 = 33; //Variablen für CallByValue-Demo
        int myVar2 = 77; //Variablen für CallByValue-Demo
        myIntegerClass myObj1 = new myIntegerClass(33); //Variablen für CallByReference-Demo
        myIntegerClass myObj2 = new myIntegerClass(77); //Variablen für CallByReference-Demo
    
        //CallByValue-Demo
        System.out.println("Vorher: " + myVar1 + " und " + myVar2);
        tausche_CallByValue(myVar1, myVar2);
        System.out.println("Nachher: " + myVar1 + " und " + myVar2 + " CallByValue");
    
        //CallByReference-Demo
        System.out.println("Vorher: " + myObj1.theValue + " und " + myObj2.theValue);
        tausche_CallByReference(myObj1, myObj2);
        System.out.println("Nachher: " + myObj1.theValue + " und " + myObj2.theValue + " CallByReference");
      } //End-Of-Main 
    
      //Funktion für CallByValue-Demo
      public static void tausche_CallByValue(int myVar1, int myVar2)
      {
        int myLocalVar3 = myVar1;  //Hilfsvariable für Tausch
        myVar1 = myVar2;           //Erster Tausch
        myVar2 = myLocalVar3;      //Rücktausch
      }
    
      //Funktion für CallByReference-Demo
      public static void tausche_CallByReference(myIntegerClass myObj1, myIntegerClass myObj2)
      {
        myIntegerClass myLocalObj3 = new myIntegerClass(myObj1.theValue);  //Hilfsvariable
        myObj1.theValue = myObj2.theValue;       //Erster Tausch
        myObj2.theValue = myLocalObj3.theValue;  //Rücktausch
      }
    }
    




    Praxisaufgabe ∇ AUFGABEN
    ∇ LÖSUNGEN