Java-Basics
(Version: 17. April 2023)
1.1 GIT-Repository als ePortfolio
1.2 Was ist Java?
1.3 Java-Installation
1.4 Programmentwurf
1.5 Erste Schritte in der Java-Programmierung
1.6 Installation einer IDE und SDK
2. Java Grundkenntnisse
2.1 Programmheader und Kommentare
2.2 Sourcecode-Formatierung
2.3 Variablen und Datentypen
2.4 Die Feldvariable/Array
2.5 Die Operatoren
2.6 Die Ein- und Ausgabe über die Konsole
2.7 Die Sequenz
2.8 Die Selektion/Mehrfachselektion
2.9 Die Iteration
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.
- 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
- Laden Sie den Java-Compiler von der Oracle-Webseite herunter: Java-Download
- 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)
- 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.
- 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)
- 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." ∇ 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 */
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?»
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 |
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);
∇ 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 } }
∇ AUFGABEN
∇ LÖSUNGEN