Wiederholung¶
Datentypen¶
Wertetypen¶
- es wird der konkrete Wert in der Variablen gespeichert
- belegen abhängig vom Datentyp unterschiedlich viel Arbeitsspeicher → unterschiedliche Wertebereiche
// boolean für Wahrheitswerte true und false
boolean x = false; // mit false initialisiert
boolean wahr = true; // Schluesselwort true
boolean falsch = !wahr; // Enthaelt den Wert false
// byte, short, int, long für ganze Zahlen
byte a = 0; // mit 0 initialisiert
short b = 124; // Zuweisung einer ganzen Zahl
int c = a + b; // Wertebereich immer beachten!
long lo = c * 200; // l nicht als Variablenname verwenden
// char für ein einzelnes Zeichen - einfache Anfuehrungszeichen!
char zeichen = 'c'; // Doppelte " für Strings!
// float und double für Fließkommazahlen
float f = 0.0f; // Wird mit 0.0 initialisiert
double d = 100.25; // Dezimalpunkt statt Dezimalkomma!!
Referenztypen¶
- es wird eine Referenz auf ein Objekt gespeichert
- Objekte besitzen Eigenschaften: Methoden (Verhalten) und Variablen (Zustand)
- Zugriff auf Methoden und Variablen über den Punkt-Operator
// Schluesselwort class um neue Klasse (Typ) zu definieren
public class Power
{
int base; // Objektvariablen, für jedes Objekt
int exp; // existieren Kopien im Arbeitsspeicher
public Power(int base, int exp)
{
this.base = base;
this.exp = exp;
}
// Objektmethode
public void print()
{
System.out.println(this.base + "^" + this.exp);
}
}
// irgendwo im Hauptprogramm
public static void main(String[] args)
{
Power p1 = new Power(2,4);
Power p2 = new Power(2,4);
p1.print();
p2.print();
boolean equal = (p1 == p2); // true oder false ?
}
Datenstrukturen¶
Variablen¶
- einfachste Datenstruktur → speichern genau einen Wert (kann auch Referenz auf ein Objekt sein)
- sind von einem Datentyp (Wertetyp oder Referenztyp)
- haben einen Namen
- besitzen einen Wert
- Wert kann sich bei Variablen zur Laufzeit ändern (Wertzuweisungsoperator =)
- Wert von Konstanten kann nicht mehr geändert werden (final)
- besitzen einen Gültigkeitsbereich (den sogenannten Scope)
- Scope einer Variable: der Anweisungsblock, in dem die Variable deklariert wurde
- lokale Variable: in einer Methode deklariert → dort lokal
- globale Variable: in einer Klasse deklariert → dort global
int zahlen = 0; // konventionelle Variable
final float PI = 3.14516f; // Konstante
// Erzeugen eines neuen Feldes (Arrays) und Zuweisen der Referenz
int[] zahlenFeld = new int[100];
// Klassen mit Objekt- und Klassenvariablen
public class Power
{
int base; // Objektvariablen, für jedes Objekt
int exp; // existieren Kopien im Arbeitsspeicher
public Power(int base, int exp)
{
this.base = base;
this.exp = exp;
}
public String toString()
{
// lokale Variable --> nur in der Methode gueltig
String output = this.base + "^" + this.exp;
return output;
}
// Variablen sollten mit einem Kleinbuchstaben beginnen
// Konstanten sollten nur mit Großbuchstaben benannt werden
}
Arrays¶
- Arrays sind Objekte (Referenztypen)
- speichern eine festgelegte Anzahl von Werten eines festgelegten Datentyps (können Werte- und Referenztypen sein)
- besitzen Methoden (z.B.
sort()
) und Variablen (z.B.length
) - können auch mehrdimensional sein
- Zugriff auf Elemente mit eckigen Klammern unter Verwndung des Indexes
// Einfache Deklaration -> nur Datentyp bekannt, keine Laenge
int[] zahlen; // Eckige Klammern für Array-Datentyp
// Erzeugen eines neuen Feldes und Zuweisen der Referenz
zahlen = new int[100]; // new-Operator, hier: Angabe der Laenge
// Explizite Initialisierung bei der Deklaration
// Bei Initialisiert mit Werten --> keine Laengenangabe noetig
float[] werte = new float[10]; // Platz für 10 Werte
double[] fib = { 1.0, 2.0, 3.0 }; // Werte in {}-Klammern
// Mehrdimensionale Felder
int[][] hdBild = new int[1920][1080];
// Felder können für alle Datentypen verwendet werden
Power[] potenzen = new Power[5]; // Noch keine Objekte enthalten
potenzen[0] = new Power(2, 4); // Zuweisung Objekt an Index 0
Literale¶
- sind Werte
- sind von einem bestimmten Datentyp (kann auch ein Referenztyp sein)
- keine ausführbare Einheit → kein Ausdruck → können nicht alleine stehen
- meistens
- auf der rechten Seite einer Wertzuweisung
- als Parameterwert bei Aufruf einer Methode
- als Vergleichswert bei logischen Ausdrücken
Ausdrücke¶
- kleinste ausführbare Einheit eines Programms
-
z.B.:
- Zuweisung
- numerische Berechnung
- logische Bedingung
-
Operatoren:
- arithmetisch:
+, -, *, /, %, ++, --
- relational:
==, !=, <, >, <=, >=
- logisch:
!, &&, ||
- Zuweisung:
=, +=, -=, *=, /=, %=
- sonstige:
a?b:c, typecast, new, instanceof, .
- arithmetisch:
Anweisungen¶
- Einzelanweisung
;
- Block
{}
- Deklaration
Typ Methode
,Typ Variable
- Ausdruck
- Bedingung
if() else switch() case
- Schleifen
for while do while
- Exceptions
try catch()
- Assertions
assert
// Bloecke immer zwischen geschweiften Klammern
{
// Anweisung 1...
// Anweisung 2...
}
// Definition und Deklaration
int a;
float b = 1.0f;
// Einfacher Ausdruck
a = 1010;
// Bedingungen
if (a == 1010) // if-Zweig muss immer vorhanden sein
{
// Anweisungen...
}
else if (a == 1011) // 0 - beliebig viele else-if-Zweige
{
// Anweisungen...
}
else // 0 - 1 else-Zweig
{
// Anweisungen...
}
switch (a) // zu pruefende Variable in Klammern
{
case 1010: { b*=2.0; break; } // beliebig viele case-Zweige
case 1011: { b*=3.0; break; } // break nicht vergessen
default: { b=0.0f; } // Zweig für alle anderen Werte
}
int[] meinFeld = {1,2,3,4,5};
// for-Schleife
// 1. Initialisierung
// 2. Schleifen-Bedingung
// 3. Anweisungen nach Schleifendurchlauf
// Schleifen-Anweisungen als Block hinter for
for (int index = 0; index < meinFeld.length; index++)
{
System.out.println(meinFeld[index]);
}
// while-Schleife
// Nur Schleifen-Bedingung angeben
// Auf Endlos-Schleifen achten
while (index < meinFeld.length)
{
System.out.println(meinFeld[index]);
index++;
}
Methoden¶
- wiederverwendbare und/oder logisch abgeschlossene Programmteile sollten in Methoden zusammengefasst werden
- Methoden besitzen einen Namen (im Scope eindeutig), keinen, einen oder mehrere Parameter und einen Rückgabetyp (evtl.
void
) - Aufruf einer Methode durch Namen und runde Klammern, in welche Werte für die Parameter übergeben werden
- bei Aufruf einer Methode werden alle Anweisungen abgearbeitet, die in der Methode definiert (implementiert) wurden (bis zum
return
) - werden Methoden mit Rückgabetyp (also nicht
void
) aufgerufen, entspricht ihr Aufruf einem konkreten Wert (Rückgabewert)
Vererbung¶
- Weitergabe aller Methoden und Objekte einer Elternklasse (Basisklasse) an eine Kindklasse (Subklasse)
- Schlüsselwort
extends
- jede Klasse (bis auf
Object
) besitzt genau eine Elternklasse; ist keine angegeben, ist es automatischObject
- Vererbung beschreibt „is-a“-Beziehung
- Methoden der Elternklasse können überschrieben werden (wenn sie nicht als
final
definiert wurden) → @Override verwenden - Originalmethode kann mit Schlüsselwort
super
aufgerufen werden
Konstruktoren¶
- besondere Objektmethoden, die beim Erzeugen des Objektes aufgerufen werden
- gleicher Name wie die Klasse, keinen Rückgabetyp angeben (auch nicht
void
) - wird kein Konstruktor definiert, steht automatisch (implizit) ein parameterloser Konstruktor zur Verfügung (einzige Ausnahme: in der Elternklasse existieren nur parametrisierte Konstruktoren)
- Aufruf von
this
undsuper
möglich - werden nicht vererbt
public class Power {
int base;
int exp;
public Power()
{
this(1,0); // Aufruf des Konstruktors in Zeile 15
}
public Power(int base)
{
this(base, 0); // Aufruf des Konstruktors in Zeile 15
}
public Power(int base, int exp)
{
this.base=base; // Parameterwerte für base und int
this.exp=exp; // auf Objektvariablen sichern
}
}
public static void main(String[] args) {
Power p1 = new Power(); // p1.base = 1, p1.exp = 0
Power p2 = new Power(10); // p2.base = 10, p2.exp = 0
Power p3 = new Power(10,5); // p3.base = 10, p3.exp = 5
}
Access Controller (Zugriffsmodifizierer)¶
- Steuerung der Lebensdauer, Sichtbarkeit und Veränderbarkeit von Klassen, Methoden und Variablen – Prinzip der Datenkapselung
-
Klassen
public
Zugriff von außerhalb des Paketes- (default) Zugriff nur innerhalb des Paketes
final
von der Klasse können keine Klassen erbenabstract
Klasse besitzt Methoden, die keinen Methodenrumpf besitzen (nicht implementiert sind)
-
Variablen und Methoden (wenn Klasse
public
)public
Zugriff von überall (bei Variablen vermeiden!)protected
Zugriff aus Klasse und Kindklasse- (default) Zugriff aus Klasse und Paket
private
Zugriff nur aus Klasse
"Kleinigkeiten", die wir (fast) nicht betrachtet haben¶
Das Schlüsselwort static
¶
Mit dem Schlüsselwort static
beschreiben wir eine Eigenschaft der Klasse. Das Schlüsselwort static
kann verwendet werden für
- Die Deklaration einer (Klassen-)Variablen. Eine Klassenvariable existiert für die Klasse genau ein Mal (alle Objekte der Klasse "teilen" sich diese eine Variable).
- Die Deklaration einer (Klassen-)Methode. Eine statische Methode (Klassenmethode) kann aufgerufen werden, ohne ein Objekt der Klasse zu erzeugen. Wir werden häufig statische Methoden in der Programmklasse (die Klasse mit der
main()
-Methode) erstellen, um diese in dermain()
-Methode aufzurufen, ohne vorher ein Objekt der Programmklasse erzeugen zu müssen. - Die Deklaration einer Klasse, wenn diese eine Klasse in einer anderen Klasse ist. Dazu kommen wir später.
Der Zugriff auf eine statische Variable bzw. statische Methode erfolgt ebenfalls per Punktnotation, aber vor dem Punkt steht dann der Klassenname. Häufig wird der Klassenname und der Punkt aber auch weggelassen.
Die for-each-Schleife¶
Wir haben ein Array stets so durchlaufen, dass wir in einer for
-Schleife jeden möglichen Index für das Array erzeugt haben und dann über den Index auf den Wert der einzelnen Elemente zugegriffen haben, also z.B.:
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9 };
for (int index = 0; index < args.length; index++)
{
System.out.print(numbers[index]);
}
Wenn wir alle Elemente eines Arrays lesen wollen, können wir das aber auch so machen:
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9 };
for(int number : numbers)
{
System.out.print(number);
}
Wir bezeichnen diese Schleifensyntax auch als for-each-Schleife. Die allgemeine Syntax ist
for(TypElement variablenNameElement : nameDesArrays)
{
// hier lesende Verwendung von variablenNameElement
}
Beachten Sie, dass Sie mit der for-each-Schleife nur lesenden Zugriff auf die Elemente des Arrays haben. Wenn Sie z.B. schreiben
, dann ändert das nichts an den Werten im Array! Beachten Sie aber auch, dass das Element ja auch eine Referenz auf ein Objekt sein könnte, dann verhält es sich natürlich anders, wenn Sie dafür Objektmethoden aufrufen, die Änderungen am Objekt bewirken (Setter).
Die Klasse java.util.Arrays
¶
Die Klasse Arrays
aus dem java.util
-Paket hat einige nützliche Methoden für Arrays. Insbesondere interessant dürfte die toString()
-Methode sein. Wir werden diese Klasse manchmal verwenden, wenn wir uns z.B. keine eigenen toString()
-Methoden für unsere Arrays schreiben wollen. Die Dokumentation der Klasse Arrays
finden Sie z.B. hier. Beachten Sie, dass Sie zum Verwenden der Klasse das java.util
-Paket importieren müssen.
Weitere interessante Methoden dieser Klasse sind copyOf()
, binarySearch()
und sort()
.
Übung
Ist die toString()
-Methode der Arrays
-Klasse statisch oder handelt es sich um eine Objektmethode?
Reservierter Typname var
¶
Es gibt Programmiersprachen, z.B. JavaScript, in denen wir Variablen nicht mit einem Typ deklarieren, sondern der Typ aus dem Wert der Variablen ermittelt wird. Java ist dagegen eine typsichere Sprache. Seit Java 10 kann aber ein reservierter Typ verwendet werden, der auf lokale Variablen anwendbar ist. Dieser reservierte Typ heißt var
. Es handelt sich dabei gar nicht um ein reserviertes Schlüsselwort, um bereits existierende Variablennamen var
oder (noch schlechter) Methodennamen var
nicht in Konflikt geraten zu lassen. Mögliche Einsätze sind wie folgt:
int[] intArray = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for(var value : intArray)
{
System.out.printf(value + " ");
}
System.out.println();
for(var index = 0; index < intArray.length; index++)
{
intArray[index] = intArray[index] + 1; // mit Effekt
}
Auch hier wird der eigentliche Typ der Variablen value
und index
aus dem Kontext ermittelt (beides int
). Man spricht von Local Variable Type Interference. Wir werden var
später bei typisierten Collections häufiger anwenden. Es macht den Code manchmal besser lesbar. Guidelines zur Verwendung von var
finden sich z.B. hier.
Textblöcke¶
Wir wissen, dass String-Literale in doppelten Hochkomma gesetzt werden, also z.B.
Bei längeren Zeichenketten, die über mehrere Zeilen gehen und auch noch Zeichen enthalten, die maskiert werden müssen, kann das sehr umständlich aussehen, z.B.:
Das geht mit Textblöcken einfacher:
Syntaktisch sind die String-Literale alse in dreifache Hochkommata eingeschlossen: """
. Achten Sie aber darauf, dass nach den öffnenden Hochkommata nicht gleich das String-Literal kommt, sondern erst ein Zeilenumbrauch:
Wichtig ist der Zeilenumbruch nach den ersten drei Hochkommata. Ansonsten können Sie Textblöcke als ganz normale Strings verwenden, d.h. wir können Textblöcke mithilfe von +
mit anderen String-Literalen verbinden und wir können Textblöcke als Parameterwerte übergeben, wenn Strings erwartet werden.
In Textblöcken klappt sogar der Einzug gut, d.h.
gibt
auf der Konsole aus. Für weitere Informationen zu Textblöcken siehe z.B. hier.
Record-Klassen¶
Record-Klassen sind spezielle Java-Klassen, die eine einfache Bildung neuer Datentypen ermöglichen. Die Grundidee ist, dass man einen Konstruktor angibt, dem als Parameter alle Werte für die Objektvariablen übergeben werden. Die Getter
(leider ohne get
davor), die toString()
-, equals()
- und hashCode()
-Methoden werden implizit definiert. Einen record definieren wir mithilfe des Schlüsselwortes record
:
Das ist es schon. Diese Klasse kann nun so verwendet werden, wie wir es kennen, z.B.:
Rectangle r1 = new Rectangle(4.0, 8.0);
Rectangle r2 = new Rectangle(4.0, 8.0);
Rectangle r3 = new Rectangle(8.0, 4.0);
System.out.println(r1.toString());
System.out.println(r2.toString());
System.out.println(r3.toString());
System.out.println("r1 gleich r2 ? " + r1.equals(r2));
System.out.println("r1 gleich r3 ? " + r1.equals(r3));
ergibt folgende Ausgabe:
Rectangle[length=4.0, height=8.0]
Rectangle[length=4.0, height=8.0]
Rectangle[length=8.0, height=4.0]
r1 gleich r2 ? true
r1 gleich r3 ? false
Die record
-Definition von Rectangle
entspricht der folgenden Klassendefinition:
public final class Rectangle
{
private final double length;
private final double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double length() { return this.length; }
double width() { return this.width; }
// Implementation of equals() and hashCode(), which specify
// that two record objects are equal if they
// are of the same type and contain equal field values.
public boolean equals...
public int hashCode...
// An implementation of toString() that returns a string
// representation of all the record class's fields,
// including their names.
public String toString() {...}
}
Beachten Sie, dass record
-Klassen automatisch final
sind und dass man also nicht von ihnen erben kann. Außerdem sind record
-Klassen stets unveränderlich (die Objektvariablen erhalten genau einmal einen Wert und sind final
).