EiP Folien - Theoretische Informatik

Werbung
Einführung in die Programmierung
mit Java
Teil 1: Grundlagen
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
13. Oktober 2015
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-1
Inhalt Teil 1: Grundlagen
1
Organisatorisches
2
Was ist Informatik
3
Geschichte von Java
4
Das erste Programm
5
Verwendung einfacher Objekte
6
Dokumentation mit Javadoc
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-2
Organisatorisches
Organisation
Vorlesung
Rechnerkennung
Übungsbetrieb (Anmeldung ab 20.10. per UniworX:
http://uniworx.ifi.lmu.de)
Klausur
Schein ist verpflichtend
WWW Seite der Vorlesung
http://www.tcs.ifi.lmu.de/lehre/ws-2015-16/eip
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-3
Organisatorisches
Begleitliteratur
Die VL richtet sich nach C.Horstmann: Big Java, 2007.
Folien werden im Netz bereitgestellt, zur Verwendung als
Notizbuch.
Weitere Java Ressourcen finden Sie im WWW.
Z.B. “Java ist auch eine Insel”.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-4
Organisatorisches
Inhalt der Vorlesung
Einführung: Informatik, Java, das erste Programm. in Java:
Datentypen, Kontrollstrukturen
Objektorientierte Programmierung: Klassen, Objekte,
Vererbung, Schnittstellen
Das Java Typsystem, generische Typen.
Algorithmen für Listen und Bäume, insbes. Balancierung von
binären Suchbäumen
Nebenläufigkeit und Threads
Entwicklung von grafischen Benutzeroberflächen (optional)
Softwaretechnik: Testen, Modellierung mit UML,
Entwurfsmuster, Verifikation mit Hoare Logik
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-5
Was ist Informatik
Was ist Informatik?
Von franz. informatique (= information + mathématiques).
Engl.: computer science, neuerdings auch informatics.
DUDEN Informatik: Wissenschaft von der systematischen
Verarbeitung von Informationen, besonders der automatischen
Verarbeitung mit Computern.
Gesellschaft f. Inf. (GI): Wissenschaft, Technik und
Anwendung der maschinellen Verarbeitung und Übermittlung
von Informationen.
Association vor Computing Machinery (ACM): Systematic
study of algorithms and data structures.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-6
Was ist Informatik
Teilbereiche der Informatik
Technische Informatik
Aufbau und Wirkungsweise von Computern
Praktische Informatik
Konstruktion von Informationsverarbeitungssystemen sowie
deren Realisierung auf Computern
Theoretische Informatik
Theoretische und verallgemeinerte Behandlungen von Fragen
und Konzepten der Informatik
Angewandte Informatik
Verbindung von Informatik mit anderen Wissenschaften
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-7
Was ist Informatik
Typische Arbeitsgebiete
Algorithmen und Komplexität
Betriebssysteme
Bioinformatik
Datenbanken
Grafik
Medieninformatik
Programmiersprachen und Compiler
Rechnerarchitektur und Rechnernetze
Robotik
Simulation
Softwareentwicklung
Spezifikation, Verifikation, Modellierung
Wirtschaftsinformatik
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-8
Was ist Informatik
Algorithmusbegriff
Muh.ammad ibn Mūsā al-Khwārizmı̄ schreibt um 830 das Lehrbuch
al-Kitāb al-muhtas.ar fı̄ h.isāb al-ğabr wa- -l-muqābala (Handbuch des
˘ Ergänzen und Ausgleichen)
Rechnens durch
Al Chwarizmi
Quelle: Wikimedia, gemeinfrei
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-9
Was ist Informatik
Algorithmen und Programmiersprachen
Algorithmus:
Systematische, schematisch ausführbare Verarbeitungsvorschrift.
Alltagsalgorithmen:
Kochrezepte, Spielregeln, schriftliches Rechnen.
Bedeutende Algorithmen:
MP3-Komprimierung, RSA Verschlüsselung, Textsuche,
Tomographie,. . .
Programmiersprachen:
erlauben eine ausführbare Beschreibung von Algorithmen. Sie
unterstützen Wiederverwendung und Kapselung und erlauben
dadurch die Erstellung großer Softwaresysteme.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-10
Geschichte von Java
Die Programmiersprache Java
Entstand Anfang der 1990 aus einem Projekt bei Sun Microsystems
zur Programmierung elektronischer Geräte (Set top boxen,
Waschmaschinen, etc.). Leiter: James Gosling.
Wurde dann zur plattformunabhängigen Ausführung von
Programmen in Webseiten (“Applets”) verwendet.
Seitdem stark expandiert, mittlerweile neben C++ die beliebteste
Sprache. Außerdem Hauptsprache für Android-Anwendungen (seit
2008).
Weiterentwicklungen: C#, Scala.
Außerdem Tendenz zu Skriptsprachen (JavaScript, PHP, etc)
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-11
Geschichte von Java
Vorteile von Java
Sicherheit
Portierbarkeit (plattformunabhängig durch JVM)
(Relativ) sauberes Sprachkonzept (Objektorientierung von
Anfang an eingebaut)
Verfügbarkeit großer Bibliotheken
Nachteile von Java
Teilweise kompliziert und technisch, da nicht für Studenten
entworfen (wie Pascal)
Zu groß für ein Semester
Ausführung relativ langsam und speicherplatzintensiv.
Alternativen: C#, Scala, Python, Haskell
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-12
Geschichte von Java
Die Java Virtual Machine
Java Programme werden vom Java Compiler übersetzt in eine Folge
von elementaren Befehlen (Bytecodes), die von einer virtuellen
Maschine (JVM) ausgeführt werden.
Die JVM verwendet einen Stapel (stack) zur Speicherung von
Zwischenergebnissen.
Beispiel:
iload
bipush
if_icmpgt
40
100
240
1
Lege den Inhalt der Speicherstelle 40 auf den Stapel.
2
Lege den Wert 100 auf den Stapel.
3
Falls der erste Wert größer als der zweite ist, dann springe zur
Speicherstelle 240 (ansonsten führe den nächsten Befehl aus).
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-13
Geschichte von Java
Plattformunabhängigkeit
Die JVM, die den Bytecode ausführt, ist auf unterschiedlichen
Plattformen (Windows, Unix, Mac, Smartphone (Android))
implementiert.
Damit kann Java Bytecode auf all diesen Plattformen ausgeführt
werden.
Eine Windows .exe Datei kann dagegen nur unter Windows
ausgeführt werden.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-14
1.Programm
Das erste Java Programm
public class Hello
{
public static void main(String[] args)
{
/* Hier findet die Ausgabe statt */
System.out.println("Hello, World!");
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-15
1.Programm
Durchführung am Rechner
Um es auszuführen müssen wir
Eine Datei Hello.java anlegen
In die Datei den Programmtext schreiben
Den Java Compiler mit dieser Datei aufrufen. Er erzeugt dann
eine Datei Hello.class, die die entsprechenden JVM Befehle
enthält. Bei Unix: javac Hello.java.
Diese Datei mit der JVM ausführen. Bei Unix: java
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
Hello.
1-16
1.Programm
Anatomie unseres Programms
Einrückungen etc. spielen keine Rolle.
Kommentare werden in /*. . . */ eingeschlossen. Sie werden
von javac ignoriert.
Alternativ kann ein einzeiliger Kommentar auch mit //
begonnen werden, dieser Kommentar geht nur bis zum Ende
der Zeile und benötgt kein schliessenden Zeichen.
Zwischen den Mengenklammern steht die Definition der
Klasse.
Das Schlüsselwort public besagt, dass diese Klasse
“öffentlich” sichtbar ist, im Gegensatz zu private.
Wir brauchen uns im Moment nur zu merken, dass ein Programm
in so eine Klassendefinition eingeschlossen werden muss und dass
Klassenname und Dateiname übereinstimmen müssen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-17
1.Programm
public static void main(String[] args){· · · }
definiert eine Methode des Namens main in der Klasse Hello.
Zwischen den Mengenklammern steht die Definition der
Methode.
Die Methode des Namens main wird automatisch beim
Programmstart ausgeführt. Andere Methoden, werden
innerhalb des Programms aufgerufen. Etwa berechneZinsen,
verschiebeRaumschiff, openConnection, . . .
Das Schlüsselwort static bedeutet, dass main im Prinzip eine
Funktion (und keine “richtige” Methode) ist. Mehr dazu später.
Der Parameter String[] args erlaubt es, dem Programm
beim Aufruf Daten, etwa einen Dateinamen mitzugeben. Man
darf ihn nicht weglassen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-18
1.Programm
Keine Angst
All das erklären wir erst viel später. Für den Anfang merken wir
uns, dass Programme immer so aussehen müssen:
public class Klassenname
{
public static void main(String[] args)
{
Hier geht’s los
}
}
und in einer Datei Klassenname.java stehen müssen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-19
1.Programm
Statements
Die Methodendefinition (der Programmrumpf) besteht aus einer
Folge von Statements (deutsch: “Befehlen”).
Hier haben wir nur ein Statement:
System.out.println("Hello, World!");
Statements enden immer mit Semikolon (Strichpunkt, ;).
Dieses Statement ruft die eingebaute Methode println des
Objektes out in der Klasse System mit dem Argument (Parameter)
"Hello, World!" auf.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-20
1.Programm
Mehrere Statements
System.out.println("Guten Tag.");
System.out.println("Urlaubsbeginn 18. Urlaubsende 31.");
System.out.println("Das macht");
System.out.println(31-18+1);
System.out.println("Urlaubstage.");
System.out.println("Auf Wiedersehen.");
Hier ist 31-18+1 ein arithmetischer Ausdruck. Sein Wert wird
berechnet und von println ausgegeben.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-21
1.Programm
Mehrere Statements
Will man keine Zeilenumbrüche, kann man auch die Methode
print verwenden.
System.out.println("Guten Tag.");
System.out.println("Urlaubsbeginn 18. Urlaubsende 31.");
System.out.print("Das macht ");
System.out.print(31-18+1);
System.out.println(" Urlaubstage.");
System.out.println("Auf Wiedersehen.");
Ergebnis:
Guten Tag.
Urlaubsbeginn 18. Urlaubsende 31.
Das macht 14 Urlaubstage.
Auf Wiedersehen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-22
1.Programm
Escapesequenzen
Ein " beginnt und endet eine Zeichenkette. Will man das Symbol "
selbst drucken, so muss man \" verwenden.
Das Symbol \ selbst kriegt man durch \\.
Es gibt noch andere solche Escapesequenzen, z.B. \n:
Zeilenumbruch.
System.out.println("Die Zeichen \" und \\ erhält man
durch Vorausstellen eines \\.\n Das war's.");
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-23
Objekte
Klassen und Objekte
Objekte enthalten Werte und Methoden (um aus diesen Werten
Ergebnisse zu berechnen und um diese Werte zu verändern).
Klassen dienen als Muster für Objekte.
Die Klasse spezifiziert die Formate der Werte, und definiert die
Methoden.
Beispiel: die eingebaute Klasse Rectangle. Der Ausdruck
new Rectangle(5, 10, 20, 30)
erzeugt ein Objekt der Klasse Rectangle mit linker oberer Ecke
(5,10) und Breite/Höhe 20/30.
Das Statement
System.out.println(new Rectangle(5,10,20,30));
gibt das Objekt aus:
java.awt.Rectangle[x=5,y=10,width=20,height=30]
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-24
Objekte
Beispielprogramm
import java.awt.Rectangle;
public class Rechteck
{
public static void main(String[] args)
{
System.out.println("Guten Tag.");
System.out.println(new Rectangle(5,10,20,30));
}
}
Die Deklaration import java.awt.Rectangle; importiert den
Klassennamen aus der package java.awt.
Alternativ kann man auch java.awt.Rectangle schreiben.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-25
Objekte
Programmvariablen
Durch das Statement
Rectangle cornflakesPackung;
wird eine Programmvariable (kurz Variable) des Typs
Rectangle deklariert.
Man kann der Variablen einen Wert zuweisen durch =. Z.B.
cornflakesPackung = new Rectangle(5,10,20,30);
Und dann ausgeben:
System.out.println(cornflakesPackung);
Liefert:
java.awt.Rectangle[x=5,y=10,width=20,height=30]
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-26
Objekte
Die Programmvariable enthält einen Verweis auf ein Objekt.
cornflakesPackung =
Rectangle
x=5
y=10
width=20
height=30
Schreiben wir
Rectangle frostiesPackung =
cornflakesPackung;
frostiesPackung =
cornflakesPackung=
(Beachte, Deklaration und Zuweisung gehen
in einem. )
Dann ist frostiesPackung auch ein Verweis auf das erzeugte Objekt.
Es gibt aber nach wie vor nur eins!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Rectangle
x=5
y=10
width=20
height=30
Grundlagen
1-27
Objekte
Methoden
Die Klasse Rectangle enthält die Methode translate zum
Verschieben eines Rechtecks. So verwenden wir sie:
cornflakesPackung.translate(15,25);
Geben wir jetzt cornflakesPackung aus, dann erhalten wir
java.awt.Rectangle[x=20,y=35,width=20,height=30]
Was kommt heraus, wenn wir
frostiesPackung ausgeben?
Antwort:
Genau
dasselbe,
da
ja
frostiesPackung
und
cornflakesPackung auf dasselbe Objekt verweisen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-28
Javadoc
Dokumentation mit Javadoc
Mit javadoc können bestimmte Kommentare zur Erzeugung von
HTML (mit Internet Browser lesbarer) Dokumentation verwendet
werden:
Ein Javadoc Kommentar beginnt immer mit /** und endet
mit */.
Zeilen innerhalb eines Javadoc Kommentars dürfen mit *
beginnen.
Ein Javadoc Kommentar soll immer vor einer Deklaration
stehen (Klasse, Methode, . . . )
weitere Regeln: siehe Beispiele und man javadoc.
Der Befehl javadoc -version -author Datei.java erzeugt eine
Datei Datei.html, die eine schön formatierte Dokumentation
enthält.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-29
Javadoc
Beispiel
/**
* Enthaelt eine Methode zur Ausgabe einer Grussbotschaft.
* @author Martin Hofmann
* @version 0.1
*/
public class Hello
{
/** Gibt die Grussbotschaft aus.
* @param args Kommandozeilenparameter
*/
public static void main(String[] args)
{
/* Hier findet die Ausgabe statt */
System.out.println("Hello, World!");
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-30
Javadoc
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-31
Javadoc
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-32
Javadoc
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grundlagen
1-33
Einführung in die Programmierung
mit Java
Teil 2: Fundamentale Datentypen
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
20. Oktober 2015
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-34
Inhalt Teil 2: Fundamentale Datentypen
7
Fundamentale Datentypen
Die Datentypen int, double
Variablen und Zuweisung
Zeichenketten
8
Fallunterscheidungen
Basisdatentyp boolean
9
Zusammenfassung
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-35
Fundamentale Datentypen
int & double Variablen Zeichenketten
Münzwerte
public class Muenzen1
{
public static void main(String[] args) {
int zehnerl = 8; // Anzahl 10 ct Muenzen
int zwanzgerl = 4; // Anzahl 20 ct Muenzen
int fuchzgerl = 3; // Anzahl 50 ct Muenzen
double gesamt = zehnerl * 0.10 +
zwanzgerl * 0.20 + fuchzgerl * 0.50;
}
}
System.out.print("Gesamtwert = ");
System.out.println(gesamt);
Alles was hinter einem // steht (bis zum Zeilenende) ist auch
Einführung
Fundamentale Datentypen
Kommentar. Alternative
zu in/*die Programmierung
... */
Martin Hofmann, Steffen Jost
2-36
Fundamentale Datentypen
int & double Variablen Zeichenketten
Die Typen int und double
Ganze Zahlen bilden den Typ int;
Fließkommazahlen den Typ double.
In Java wird int automatisch in double konvertiert.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-37
Fundamentale Datentypen
int & double Variablen Zeichenketten
Variablen
Das Statement
int zehnerl = 8;
deklariert eine ganzzahlige Variable des Typs int mit dem Wert 8.
Man kann in Java einer schon deklarierten Variablen neue Werte
zuweisen:
zwanzgerl = 5;
int zw = zwanzgerl;
zw = 6;
System.out.print("Wert von \"zwanzgerl\": ");
System.out.println(zwanzgerl);
System.out.print("Wert von \"zw\": ");
System.out.println(zw);
Was wird gedruckt?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-38
Fundamentale Datentypen
int & double Variablen Zeichenketten
Antwort
Wert von "zwanzgerl": 5
Wert von "zw": 6
Der Grund ist, dass Integer- und
Double-Variablen keine Verweise sind
(wie Objektvariablen) sondern den jeweiligen Wert direkt enthalten.
Mit anderen Worten: eine IntegerVariable enthält einen Integer-Wert,
eine Objekt-Variable enthält eine Speicheradresse (unter der sich ein Objekt
befindet).
zw = 6
zwanzgerl = 5
frostiesPackung =
cornflakesPackung=
Rectangle
x=20
y=35
width=20
height=30
Fiktiver Speicherzustand
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-39
Fundamentale Datentypen
int & double Variablen Zeichenketten
Initialisierung
Man muss Variablen nicht initialisieren:
int a;
int b = 4;
a = b + 2;
Sie müssen aber vor der ersten Verwendung einen Wert bekommen:
int a;
int b = 4;
System.out.println(a);
ist ein Programmierfehler (erkennt Java schon beim Compilieren).
Mit Rectangle geht dies ganz analog:
Rectangle r1 = new Rectangle(10,20,30,40);
// identisch zu
Rectangle r2;
r2 = new Rectangle(10,20,30,40);
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-40
Fundamentale Datentypen
int & double Variablen Zeichenketten
Nochmal Wertzuweisung
Man kann auch schreiben:
a = a + 1;
Dadurch wird der Wert von a um eins erhöht.
Manche Programmierer verwenden dafür die Kurzform
a++;
Daher auch der Name C++ für „Nachfolger von C“.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-41
Fundamentale Datentypen
int & double Variablen Zeichenketten
Rechengenauigkeit
Es gibt es nur 232 verschiedene int-Werte. Ebenso hat double
begrenzte Genauigkeit.
Es gibt den Datentyp long, der insgesamt 264 verschiedene Werte
aufweist. Long-Konstanten schreibt man so: 1340000000000000L.
Schließlich gibt es die Klassen BigInteger und BigDecimal die
praktisch unlimitiert sind. Sie repräsentieren Zahlen als Listen von
Ziffern.
Nachteil: Langsam und umständlich zu benutzen.
import java.math.BigInteger;
...
BigInteger b = new BigInteger("100000000");
b = b.multiply(b) ; b = b.multiply(b) ; b = b.multiply(b);
b = b.subtract(new BigInteger("1"));
System.out.println(b);
Druckt:
9999999999999999999999999999999999999999999999999999999999999999
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-42
Fundamentale Datentypen
int & double Variablen Zeichenketten
Typkonversion
int euros = 2;
double gesamt = euros; // ok
double euros = 2.0;
int anzahlEuros = euros; // geht nicht
Im ersten Beispiel wird der Integer-Wert automatisch in Double
konvertiert.
Im zweiten Beispiel nicht.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-43
Fundamentale Datentypen
int & double Variablen Zeichenketten
Typkonversion
Man kann aber schreiben:
double euros = 2.50;
int anzahlEuros = (int)euros;
System.out.println(anzahlEuros);
Das ist eine explizite Typkonversion (typecast).
Hier werden einfach alle Dezimalstellen abgeschnitten.
Will man runden, so verwende man
double a = 3.759;
System.out.println((int)Math.round(a));
Die (statische) Methode Math.round berechnet den
nächstgelegenen ganzzahligen Double-Wert.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-44
Fundamentale Datentypen
int & double Variablen Zeichenketten
Rundungsfehler
double f = 4.35;
int n = (int)(100 * f);
System.out.print(n);
Druckt 434.
Grund: In Binärdarstellung ist 4,35 ein echt periodischer Bruch.
(int)Math.round(100 * f);
hat Wert 435.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-45
Fundamentale Datentypen
int & double Variablen Zeichenketten
Konstanten
int flaschen = 3;
int dosen = 5;
double mengeFanta = flaschen * 0.5 + dosen * 0.33;
ist unschön, da 0.5 und 0.33 einfach so dastehen.
Besser:
final double FLASCHEN_INHALT = 0.5;
final double DOSEN_INHALT = 0.33;
double mengeFanta = flaschen*FLASCHEN_INHALT+dosen*DOSEN_INHALT;
Werte, die mit final deklariert werden, können nur einmal
initialisiert und danach nicht mehr verändert werden.
Vorteil gegenüber Variablen: Effizienz + Dokumentation.
Numerische Konstanten wie 0.5 mitten im Programm sind
schlechter Stil .
Vordefinierte Double-Konstanten: Math.PI und Math.E.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-46
Fundamentale Datentypen
int & double Variablen Zeichenketten
Arithmetik
Plus + und Mal * hatten wir schon.
Division wird als / notiert.
Vorsicht: Sind beide Operanden von / Integers, so wird
abgerundet.
int s1
int s2
int s3
double
= 5;
= 6;
= 3;
mittelwert = (s1 + s2 + s3) / 3;
mittelwert hat den Wert 4 (statt 4.666666...)
Richtig:
double mittelwert = (s1 + s2 + s3) / 3.0;
Grund: In Java (und C, C++) ist “/” bei int die ganzzahlige
Division (“div”).
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-47
Fundamentale Datentypen
int & double Variablen Zeichenketten
Was gibt’s sonst noch
Die „Punkt vor Strich“ Regel gilt.
Mathematische Funktionen sind in der Klasse Math definiert.
Automatische Typkonversionen erfolgen von innen nach außen.
Beispiel: Die „Lösungsformel“:
double a;
double b;
double c;
/* Wertzuweisung */
x1 = (-b + Math.sqrt(b * b - 4 * a * c)) / (2 * a);
x2 = (-b - Math.sqrt(b * b - 4 * a * c)) / (2 * a);
Ebenso: a * a + b * b - 2 * a * b * Math.sin(phi);
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-48
Fundamentale Datentypen
int & double Variablen Zeichenketten
Organisatorisches
Kurs “Java für Anfänger”, 3 ECTS, ab heute!
http://www.mobile.ifi.lmu.de/lehrveranstaltungen/
java-fuer-anfaenger/
“Einführung in die Programmierung” (9 ECTS)
versus “Einführung in die Informatik” (9 oder 6 ECTS):
Studierende mit integriertem Nebenfach müssen dies mit Ihrem
eigenem Prüfungsamt klären!
Studierende mit regulärem Nebenfach “Informatik”:
Sollten “Einführung in die Informatik” hören.
Wer möchte, darf freiwillig auch diese Veranstaltung
“Einführung in die Programmierung” einbringen. Empfohlen
für Studierende mit gutem mathematischem Hintergrund!
Studierende mit Nebenfach Informatik, welche 12 ECTS
benötigen, müssen den obigen Java-Kurs besuchen: 9+3=12.
Freiwillige EIP-Hörer, welche nur “Einführung in die
Informatik” für 6 ECTS benötigen, bekommen zusätzliche
ECTS nicht angerechnet.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-49
Fundamentale Datentypen
int & double Variablen Zeichenketten
Zeichenketten
Der Datentyp String besteht aus Zeichenketten, d.h. Folgen von
Buchstaben und Sonderzeichen, eingeschlossen durch "
String name = "Matthias";
name = "Johanna";
System.out.println(name);
Druckt: Johanna.
int n = name.length();
Die Variable n hat den Wert 7.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-50
Fundamentale Datentypen
int & double Variablen Zeichenketten
Teile einer Zeichenkette
Ausdruck s.substring(anfang, endePlusEins) bezeichnet die
Teilzeichenkette von s angefangen vom Zeichen an Position anfang
bis (ausschließlich) zum Zeichen an Position endePlusEins.
Positionen beginnen immer bei Null.
Länge der Teilzeichenkette = endePlusEins - anfang
String s = "Hello, World!";
String sub1 = s.substring(0,5);
String sub2 = s.substring(4,8);
Was sind die Werte von sub1 und sub2?
Welcher substring-Ausdruck hat den Wert World ?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-51
Fundamentale Datentypen
int & double Variablen Zeichenketten
Teile einer Zeichenkette
Ausdruck s.substring(anfang, endePlusEins) bezeichnet die
Teilzeichenkette von s angefangen vom Zeichen an Position anfang
bis (ausschließlich) zum Zeichen an Position endePlusEins.
Positionen beginnen immer bei Null.
Länge der Teilzeichenkette = endePlusEins - anfang
String s = "Hello, World!";
String sub1 = s.substring(0,5);
String sub2 = s.substring(4,8);
Was sind die Werte von sub1 und sub2?
Welcher substring-Ausdruck hat den Wert World ?
Antworten:
sub1 den Wert "Hello"
sub2 den Wert "o, W"
Der Ausdruck s.substring(7,12) hat den Wert "World"
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-51
Fundamentale Datentypen
int & double Variablen Zeichenketten
Teile einer Zeichenkette
Will man alle Zeichen von anfang bis zum Ende der Zeichenkette,
dann kann man
s.substring(anfang, s.length())
schreiben. Das letzte Zeichen hat nämlich die Position
s.length() − 1.
Eine erlaubt Kurzform dafür ist auch s.substring(anfang):
String s = "01234567";
System.out.println(s.substring(3));
// gibt "34567" aus
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-52
Fundamentale Datentypen
int & double Variablen Zeichenketten
Fehlerbehandlung
Ruft man s.substring mit unpassenden Argumenten auf, so gibt
es einen Fehler. Z.B.: s.substring(4,30) führt zu:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException:
String index out of range: 20
at java.lang.String.substring(String.java:1473)
at Namen.main(Namen.java:5)
Man sagt:
“der Ausdruck wirft eine Ausnahme” (throws an exception)
Es ist möglich, so eine Ausnahme im Programm “aufzufangen” und
benutzerdefinierte Befehle auszuführen, z.B. eine ordentliche
Fehlermeldung.
Noch besser ist es, das Auftreten solcher Ausnahmen von
vornherein zu vermeiden.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-53
Fundamentale Datentypen
int & double Variablen Zeichenketten
Verkettung
Zeichenketten kann man konkatenieren.
In Java verwendet man dafür das Pluszeichen.
Der Ausdruck "Matthias" + "Johanna" hat den Wert
MatthiasJohanna.
Der Ausdruck
"Euro" + "s".substring(0,n)
hat den Wert "Euro" oder "Euros", je nachdem, ob n gleich 0
oder 1 ist. Alle anderen Werte von n sind aber nicht erlaubt!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-54
Fundamentale Datentypen
int & double Variablen Zeichenketten
Was “sind” Zeichenketten?
Eine Zeichenkette ist ein Objekt.
Es versteht u.a. die Methoden length und substring.
Die Methode length liefert die Länge zurück.
Die Methode substring ein neues String-Objekt, das den
jeweiligen Teilstring enthält.
Man kann eine Zeichenkette nie verändern
im Gegensatz zu veränderlichen Objekten wie Rectangle.
Im Computer ist ein String eine Speicheradresse.
Unter dieser Adresse befindet sich die Länge, z.B. n. In den n
darauffolgenden Speicherstellen befinden sich die Zeichen.
In Java keine Möglichkeit, diese Zeichen oder die Länge zu
verändern, obwohl die Maschinensprache das im Prinzip
zuließe.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-55
Fundamentale Datentypen
int & double Variablen Zeichenketten
Verkettung mit Zahlwerten
double betrag = 34.99;
int nummerMahnung = 2;
String anweisung = nummerMahnung + ". Mahnung: " +
"Bitte zahlen Sie " + betrag + " EUR.";
System.out.println(anweisung);
Druckt:
2. Mahnung: Bitte zahlen Sie 34.99 EUR.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-56
Fundamentale Datentypen
int & double Variablen Zeichenketten
Verkettung mit Zahlwerten
Ist ein Operand von + eine Zeichenkette, so wird der andere
automatisch in eine Zeichenkette umgewandelt. Das ist keine
Typkonversion:
String betrag = 34.99 * 2;
löst aus:
incompatible types
found
: double
required: java.lang.String
String betrag = 34.99 * 2;
Vielmehr hat das +-Zeichen je nach Typ der Operanden leicht
unterschiedliche Bedeutung. Man spricht von overloading
(Überladung).
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-57
Fundamentale Datentypen
int & double Variablen Zeichenketten
Verkettung mit Zahlwerten
Man kann schreiben ""+x um x in eine Zeichenkette umzuwandeln.
Aber Vorsicht:
String anweisung = nummerMahnung + ". Mahnung: " +
"Bitte zahlen Sie " + betrag/3 + " EUR.";
System.out.println(anweisung);
Ergebnis:
2. Mahnung: Bitte zahlen Sie 11.663333333333334 EUR.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-58
Fundamentale Datentypen
int & double Variablen Zeichenketten
Abhilfe: Formatierte Ausgabe
import java.text.NumberFormat;
..
.
NumberFormat formatierer = NumberFormat.getNumberInstance();
formatierer.setMaximumFractionDigits(2);
formatierer.setMinimumFractionDigits(2);
double betrag = 34.99;
int nummerMahnung = 2;
String anweisung = nummerMahnung + ". Mahnung: " +
"Bitte zahlen Sie " + formatierer.format(betrag/3) + " EUR.";
Ergebnis:
2. Mahnung: Bitte zahlen Sie 11,66 EUR.
Sogar Tausenderpunkte werden eingesetzt, z.B.: 1.192.279,33 EUR.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-59
Fundamentale Datentypen
int & double Variablen Zeichenketten
Beispiel: Benutzernamen
Wir möchten aus dem ersten und letzten Buchstaben des Namens
und einer laufenden Nummer einen Benutzernamen erzeugen:
String name = "Johanna";
int lfdNo = 1728;
Der Benutzername sollte Ja1728 sein.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-60
Fundamentale Datentypen
int & double Variablen Zeichenketten
Beispiel: Benutzernamen
Wir möchten aus dem ersten und letzten Buchstaben des Namens
und einer laufenden Nummer einen Benutzernamen erzeugen:
String name = "Johanna";
int lfdNo = 1728;
Der Benutzername sollte Ja1728 sein.
Kein Problem:
String benutzerName;
benutzerName = name.substring(0,1) +
name.substring(name.length() - 1) + lfdNo;
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-60
Fundamentale Datentypen
int & double Variablen Zeichenketten
Parsing von Zeichenketten
Wie erhalten wir aus einem Benutzernamen die laufende Nummer?
benutzerName.substring(2)
enthält zwar die Ziffern der lfd. Nr. ist aber immer noch ein String.
Lösung:
int nummer = Integer.parseInt(benutzerName.substring(2));
parseInt ist eine statische Methode der Klasse Integer und
dient dazu, eine Zeichenkette in einen integer umzuwandeln.
Statische Methoden werden nicht an ein Objekt geschickt, sondern
können “einfach so” ausgeführt werden.
Details später.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-61
Fundamentale Datentypen
int & double Variablen Zeichenketten
Parsing von Doubles
. . . geht analog mit Double.parseDouble, z.B.:
double c = Double.parseDouble("2.97E9"); /* oder so */
Aber Vorsicht: eine “deutsche” Zahl, wie 1.234,59 kann
parseDouble nicht verarbeiten.
Wie das geht, siehe Java Reference Manual
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-62
Fallunterscheidungen
Basisdatentyp boolean
Fallunterscheidungen
Oft will man ein Statement nur dann ausführen, wenn eine
bestimmte Bedingung gilt. Das geht mit dem if-Statement:
if (zinsSatz > 100.0) {
System.out.println("Fehler.");
} else
rate = restschuld * zinsSatz/100.0 / 12.0 + tilgung;
Bemerkung: Ausgaben an System.out sind dilettantisch.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-63
Fallunterscheidungen
Basisdatentyp boolean
Professionellere Lösung
if (zinsSatz > 100.0) {
Fehlerbearbeitung.fehler(falscherZinsSatz);
} else
rate = restschuld * zinsSatz/100.0 / 12.0 + tilgung;
Fehlerbearbeitung.fehler ist eine benutzerdefinierte
Methode, die Fehlerobjekte anzeigt und entsprechend
verfährt.
Ein (benutzerdefiniertes) Fehlerobjekt (hier
falscherZinsSatz) beinhaltet den Anzeigetext (üblicherweise
in verschiedenen Sprachen) und Instruktionen wie bei seinem
Auftreten zu verfahren ist.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-64
Fallunterscheidungen
Basisdatentyp boolean
Bedingungen
. . . stehen immer in Klammern und sind von der Form x1 op x2
wobei
op ist <, >, <=, >=, ==
Die Operanden sind Zahlen des gleichen Typs, evtl. wird
implizite Typkonversion vorgenommen.
Später lernen wir noch andere Bedingungen kennen.
Vorsicht:
Testen von Doubles auf Gleichheit ist problematisch wegen
möglicher Rundungsfehlern. Besser die Größe der Differenz testen:
double final EPSILON = 1E-10; // zum Beispiel
if ( Math.abs(x - y) <= EPSILON )
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-65
Fallunterscheidungen
Basisdatentyp boolean
Blöcke & Zusammgesetze Statments
Blöcke
Formal steht nach der Bedingung des if genau ein Statement.
Braucht man mehrere, so kann man eine Folge von Statements mit
geschweiften Klammern { } zu einem Block zusammenfassen.
Ein Block wird praktisch wie ein einzelnes Statement betrachtet.
zusammengesetzte Statements
Das ganze if - else Konstrukt wird als ein einziges Statement
aufgefasst, es ist ein zusammengesetztes Statement.
Man darf den else Teil auch komplett weglassen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-66
Fallunterscheidungen
Basisdatentyp boolean
Formale Syntax für Statements
hstatementi ::=
|
|
|
...
htype_expressioni hidenti [= hexpressioni];
{ (hstatementi)+ }
if ( hexpressioni) hstatementi [else hstatementi]
...
Durch diese sog. Backus Naur Form (kurz BNF) wird die Menge
der Statements formal definiert.
Jedes in h. . . i geschriebene Wort bezeichnete eine Menge von
Ausdrücken.
Courier-Text bezeichnet wörtliche Anteile
| trennt mehrere Alternativen voneinander
[. . . ] bedeutet “optional”
(. . . )+ bedeutet “mindestens ein oder mehrere”
(. . . )∗ bedeutet “keins oder mehrere”
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-67
Fallunterscheidungen
Basisdatentyp boolean
Was ist hier falsch?
// Fehler!
if (betrag <= kontostand)
double neuerKontostand = kontostand - betrag;
kontostand = neuerKontostand;
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-68
Fallunterscheidungen
Basisdatentyp boolean
Was ist hier falsch?
// Fehler!
if (betrag <= kontostand)
double neuerKontostand = kontostand - betrag;
kontostand = neuerKontostand;
Antwort: Die geschweiften Klammern fehlen!
Ohne geschweifte Klammern wird die dritte Zeile immer ausgeführt.
Glücklicherweise wird der Fehler hier vom Kompiler erkannt, da die
Variable neuerKontostand ja nicht immer deklariert wäre.
Im Allgemeinen ist dies aber eine mögliche Fehlerquelle
⇒ Besser immer explizit Klammern!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-68
Fallunterscheidungen
Basisdatentyp boolean
Was ist hier falsch?
double neuerKontostand = 0;
// Noch schlimmer!
if (betrag <= kontostand)
neuerKontostand = kontostand - betrag;
kontostand = neuerKontostand;
Antwort: Die geschweiften Klammern fehlen!
Ohne geschweifte Klammern wird die dritte Zeile immer ausgeführt.
Glücklicherweise wird der Fehler hier vom Kompiler erkannt, da die
Variable neuerKontostand ja nicht immer deklariert wäre.
Im Allgemeinen ist dies aber eine mögliche Fehlerquelle
⇒ Besser immer explizit Klammern!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-68
Fallunterscheidungen
Basisdatentyp boolean
Antwort
Merke Einrückungen dienen der Lesbarkeit,
werden aber vom Compiler ignoriert.
Richtig:
if (betrag <= kontostand) {
double neuerKontostand = kontostand - betrag;
kontostand = neuerKontostand;
}
Oder so:
if (betrag <= kontostand)
{
double neuerKontostand = kontostand - betrag;
kontostand = neuerKontostand;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-69
Fallunterscheidungen
Basisdatentyp boolean
Mehrere if-Statements
Natürlich können in den Zweigen eines if-Statements wiederum
solche stehen:
if (
s
else
s
else
s
else
richter >= 8.0 )
= "Grosse Verwuestung";
if ( richter >= 7.0 )
= "Viele Gebaeude zerstoert";
if ( richter >= 6.0 )
= "Viele Gebaeude beschaedigt";
if ( richter >= 4.5 )
...
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-70
Fallunterscheidungen
Basisdatentyp boolean
Dangling else
Typsicher Fehler:
if (betrag > 0)
if (kontostand > betrag)
kontostand = kontostand - betrag; // Abbuchung
else
kontostand = kontostand - betrag;
// Gutschrift
Ein else bezieht sich immer auf das nächstgelegene if.
Ohne Klammern ist dies hier das innere.
Erneut gilt: Einrückungen werden vom Compiler ignoriert.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-71
Fallunterscheidungen
Basisdatentyp boolean
Dangling else
Typsicher Fehler:
if (betrag > 0)
if (kontostand > betrag)
kontostand = kontostand - betrag; // Abbuchung
else
kontostand = kontostand - betrag;
// Gutschrift
Ein else bezieht sich immer auf das nächstgelegene if.
Ohne Klammern ist dies hier das innere.
Erneut gilt: Einrückungen werden vom Compiler ignoriert.
if (betrag > 0) {
if (kontostand > betrag)
kontostand = kontostand - betrag; // Abbuchung
} else {
kontostand = kontostand - betrag; // Gutschrift
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-71
Fallunterscheidungen
Basisdatentyp boolean
Beispiel: Maximum und Minimum
Wir wollen aus int a, b, c das
größte und das kleinste berechnen.
Die können wir mit verschachtelten
if-statments effizient erreichen.
Allerdings sollte man solche größere
Code-Blöcke nicht direkt einbauen.
Martin Hofmann, Steffen Jost
if (a >= b)
if (a >= c) {
max = a;
if (c >= b)
min = b;
else
min = c;
} else {
max = c; min = b;
}
else
if (b >= c) {
max = b;
if (c >= a)
min = a;
else
min = c;
} else {
max = c; min = a;
}
Einführung in die Programmierung
Fundamentale Datentypen
2-72
Fallunterscheidungen
Basisdatentyp boolean
Beispiel: Maximum und Minimum
Wir wollen aus int a, b, c das
größte und das kleinste berechnen.
Die können wir mit verschachtelten
if-statments effizient erreichen.
Allerdings sollte man solche größere
Code-Blöcke nicht direkt einbauen.
Man kann (und sollte) auch einfach
Bibliotheksfunktionen verwenden:
max = Math.max(a, Math.max(b,c));
min = Math.min(a, Math.min(b,c));
Vielleicht nicht so effizient, aber lesbarer?
Martin Hofmann, Steffen Jost
if (a >= b)
if (a >= c) {
max = a;
if (c >= b)
min = b;
else
min = c;
} else {
max = c; min = b;
}
else
if (b >= c) {
max = b;
if (c >= a)
min = a;
else
min = c;
} else {
max = c; min = a;
}
Einführung in die Programmierung
Fundamentale Datentypen
2-72
Fallunterscheidungen
Basisdatentyp boolean
Der Datentyp boolean
int x = 5;
System.out.println(x < 10);
Gibt aus: true
In Java ist x < 10 ein Ausdruck des Typs boolean, genauso wie
x + 12 einer vom Typ int ist.
Werte des Typs boolean sind (nur) true und false.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-73
Fallunterscheidungen
Basisdatentyp boolean
Logische Verknüpfung
Auf dem Typ boolean sind die zweistelligen Verknüpfungen &&
und || erklärt:
e1 && e2 bedeutet: “e1 und e2 ”;
bzw. “sowohl e1 , als auch e2 ”; “e1 und e2 beide true”.
e1 || e2 bedeutet: “e1 oder e2 ”;
bzw. “mindestens eins von beiden, e1 oder e2 , ist true”.
Außerdem gibt es die einstellige Operation !:
!e bedeutet: “nicht e”, “das Gegenteil von e”.
Beachte: bei e1 ||e2 wird zunächst e1 ausgewertet. Ist das Ergebnis
true, so wird e2 gar nicht erst ausgewertet. Analog für &&.
Zu Berücksichtigen, wenn Seiteneffekte auftreten können.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-74
Fallunterscheidungen
Basisdatentyp boolean
Beispiel 1
Johannas Geburtstag ist der 21.12.
Angenommen, wir haben int-Variablen day und month.
Welcher Boole’sche Ausdruck ist true genau dann, wenn die Werte
der beiden Variablen Johannas Geburtstag entsprechen?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-75
Fallunterscheidungen
Basisdatentyp boolean
Beispiel 1
Johannas Geburtstag ist der 21.12.
Angenommen, wir haben int-Variablen day und month.
Welcher Boole’sche Ausdruck ist true genau dann, wenn die Werte
der beiden Variablen Johannas Geburtstag entsprechen?
Antwort
Der Ausdruck day == 21 && month == 12
So können wir ihn verwenden:
if (day == 21 && month == 12)
System.out.println("Happy birthday, Johanna!");
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-75
Fallunterscheidungen
Basisdatentyp boolean
Beispiel 2
Wie drücken wir aus, dass die double Variable heat zwischen
100.0 und 120.0 liegt?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-76
Fallunterscheidungen
Basisdatentyp boolean
Beispiel 2
Wie drücken wir aus, dass die double Variable heat zwischen
100.0 und 120.0 liegt?
Antwort
100.0 <= heat && heat <= 120.0
Äquivalent sind auch
!(heat < 100.0) && !(heat > 120.0)
!(heat < 100.0 || heat > 120.0)
De Morgan’s Gesetz:
!(a && b) = !a || !b
!(a || b) = !a && !b
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-76
Fallunterscheidungen
Basisdatentyp boolean
Andere Boole’sche Ausdrücke
Methoden können einen boolean Wert zurückliefern:
Die Klasse String enthält die Methode equals, die einen String
Parameter hat und einen boolean zurückliefert.
String answer;
System.out.println("Koennen Sie mir helfen?");
/* Eingabe von answer */
if (answer.equals("ja") || answer.equals("Ja")) {
System.out.println("Danke!");
} else {
System.out.println("Schade.");
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-77
Fallunterscheidungen
Basisdatentyp boolean
Stringvergleiche
Es gibt auch die Methode equalsIgnoreCase, die
Groß/Kleinschreibung ignoriert.
if (antwort.equalsIgnoreCase("ja"))
System.out.println("Danke!");
Zum Vergleich von Strings soll man nicht == verwenden.
String x = "a";
System.out.println("ja" == "ja");
System.out.println("ja" == "j" + "a");
System.out.println("ja" == "j" + x);
Gibt aus: true true false
Grund: == bezeichnet hier Identität, d.h. liegen die Strings an
derselben Stelle im Speicher.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-78
Fallunterscheidungen
Basisdatentyp boolean
Internalisierung
String x = "a";
System.out.println("ja"
System.out.println("ja"
System.out.println("ja"
System.out.println("ja"
==
==
==
==
"ja");
"j" + "a");
("j" + x));
("j" + x).intern());
Gibt aus: true true false true
Grund: Methode intern schaut nach, ob ein identischer String
schon vorhanden ist und gibt ggf. diesen zurück. Ansonsten werden
String-Objekte verglichen.
Besser:
Strings vergleichen mit equals oder compareTo (nächste Folie).
Der Inhalt dieser Folie ist nicht prüfungsrelevant.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-79
Fallunterscheidungen
Basisdatentyp boolean
Stringvergleiche
Die Methode compareTo vergleicht nach der lexikographischen
Ordnung, liefert aber einen int zurück:
s1 .compareTo(s2 ) ist
< 0, wenn s1 alphabetisch vor s2 kommt
= 0, wenn s1 und s2 gleich sind
> 0, wenn s1 alphabetisch nach s2 kommt.
Beispiele:
"AAAaaaaa".compareTo("meinSchluesseldienst") ist < 0
"Vorlesung".compareTo("Vorlesen") ist > 0
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-80
Fallunterscheidungen
Basisdatentyp boolean
Boole’sche Variablen
Man kann auch Variablen des Typs boolean deklarieren:
boolean mitBedienung;
boolean tischGedeckt;
boolean draussen;
/* Initialisierung von draussen und tischGedeckt */
mitBedienung = !draussen || tischGedeckt;
if (mitBedienung)
System.out.println("Hier keine Selbstbedienung!");
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-81
Fallunterscheidungen
Basisdatentyp boolean
Übungen
Was ist an den folgenden Statements falsch ?
if cents > 0 then System.out.println(cents + " Cents");
if (1 + x > Math.pow(x, Math.sqrt(2)) y = y +x;
if (x = 1) y++; else if (x = 2) y = y + 2;
if (x && y == 0) p = new Point(x,y);
if (1 <= x <= 10) {System.out.println("Danke.");}
if (!antwort.equalsIgnoreCase("Ja ") ||
!antwort.equalsIgnoreCase("Nein"))
System.out.println("Antworten Sie mit Ja oder Nein.");
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-82
Fallunterscheidungen
Basisdatentyp boolean
Beispiel: Schaltjahre
Jedes vierte Jahr ist ein Schaltjahr, es sei denn, die Jahreszahl ist
durch hundert teilbar. In diesem Fall liegt ein Schaltjahr nur vor,
wenn die Jahreszahl durch 400 teilbar ist.
Beispiel: 2000 war ein Schaltjahr, 1900 war keins.
Man schreibe einen Boole’schen Ausdruck, der true ist, genau
dann wenn jahr ein Schaltjahr ist.
Hilfe: x % y ist der Rest der ganzzahligen Division von x durch y.
Z.B.: 12%5=2.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-83
Fallunterscheidungen
Basisdatentyp boolean
Beispiel: Schaltjahre
Jedes vierte Jahr ist ein Schaltjahr, es sei denn, die Jahreszahl ist
durch hundert teilbar. In diesem Fall liegt ein Schaltjahr nur vor,
wenn die Jahreszahl durch 400 teilbar ist.
Beispiel: 2000 war ein Schaltjahr, 1900 war keins.
Man schreibe einen Boole’schen Ausdruck, der true ist, genau
dann wenn jahr ein Schaltjahr ist.
Hilfe: x % y ist der Rest der ganzzahligen Division von x durch y.
Z.B.: 12%5=2.
Antwort
jahr % 4 == 0 && !(jahr % 100) == 0 || jahr % 400 == 0
Merke:
&& bindet stärker als ||
! bindet stärker als &&
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-83
Zusammenfassung Kapitel 2
Datentypen int, double.
Elemente und Grundoperationen
Implizite Konversion
Anwendungsbeispiele
Fallunterscheidungen
Anwendungsbeispiele
Formale Syntax mit Backus-Naur-Form
(wird hierdurch auch exemplarisch eingeführt)
Geschachtelte Fallunterscheidungen und deren Anwendung
Der Datentyp boolean: Verwendung und Grundoperationen
Der Datentyp String: Verwendung und Grundoperationen
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Fundamentale Datentypen
2-84
Einführung in die Programmierung
mit Java
Teil 3: Objekte und Klassen
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
27. Oktober 2015
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-85
Inhalt Teil 3: Objekte und Klassen
10
Objekte und Klassen
Instanzvariablen
Methoden
Variablen Arten
Lokale Variablen
Parameter
Instanzvariablen
Konstruktoren
Der spezielle Wert null
Seiteneffekte
static
Bankkonto Beispiel
11
Zusammenfassung Klassen und Objekte
Instanzvariablen
Klassenvariablen
Methoden
Konstruktoren
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-86
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Definition von eigenen Klassen
Fortlaufendes Beispiel des Kapitels
Wir wollen Bankkontos verwalten.
Ein Bankkonto enthält einen Kontostand
außerdem vielleicht noch Name des Besitzers, usw.
Der Kontostand kann
abgerufen werden
erhöht werden durch Einzahlung
erniedrigt werden durch Abhebung
In der Standarbibliothek gibt es dafür keine passende Klasse, also
schreiben wir uns diese einfach selbst!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-87
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Verwendung von Bankkonten
public class BankkontoTest {
public static void main(String[] args) {
Bankkonto matthiasGiro = new Bankkonto();
Bankkonto johannasSpar = new Bankkonto();
double zinsSatz = 1.25;
}
}
matthiasGiro.einzahlen(30000.00);
johannasSpar.einzahlen(2000.00);
matthiasGiro.abheben(10000.00);
johannasSpar.einzahlen(
johannasSpar.getKontostand()*zinsSatz/100.0);
System.out.println("Johannas Sparkonto: " +
johannasSpar.getKontostand());
System.out.println("Matthias Girokonto: " +
matthiasGiro.getKontostand());
Ausgabe:
Johannas Sparkonto: 2025.0
Matthias Girokonto: 20000.0
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-88
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Eigene Klassen definieren
Eigene Klassen-Definition folgen immer diesem Muster:
public class Bankkonto {
Deklaration der zu speichernden Daten
Definition der Konstruktoren
Definition der Methoden
}
Dies muss in einer Datei gespeichert werden, deren Namen mit dem
Klassennamen übereinstimmt.
Da hier der Klassenname Bankkonto gewählt wurde, müssen wir
die Definition also in einer Datei Bankkonto.java speichern.
Die drei Einträge dürfen in beliebiger Reihenfolge stehen, sogar
vermischt, doch obige Konvention kann die Lesbarkeit erleichtern.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-89
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Deklaration der Daten
Die im Objekt gespeicherten Daten bestehen aus Variablen, den
Instanzvariablen, die in der Klasse deklariert werden.
Im Bankkonto-Beispiel brauchen wir (zunächst) nur eine
Instanzvariable vom Typ double.
Wir schreiben also bei Deklaration der zu speichernden Daten:
private double kontostand;
Damit wird gesagt, dass jedes Objekt der Klasse Bankkonto sich
einen Double-Wert “merken” kann.
Die Qualifikation private besagt, dass dieser Wert von außen
nicht direkt einsehbar ist. Nur Aufrufe von Methoden dieser Klasse
können darauf zugreifen. Methoden können solche Werte aber nach
aussen reichen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-90
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Instanzvariablen als public
Man darf Instanzvariablen auch public deklarieren. Dann kann
man ihren Wert von außen abrufen.
Beispiel: Klasse Rectangle definiert height als public.
Rectangle r = Rectangle(10,10,30,50);
System.out.println(r.height);
Aber es ist meist schlecht, Instanzvariablen public zu deklarieren!
Grund: Veränderungen der Deklaration in späteren Versionen sind
dann problematisch.
Beispiel: Kontostand als int (in Cents) anstatt double?!
Gute Modularisierung erhöht die Wartbarkeit des Codes!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-91
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Rectangle
Ein Rectangle besteht aus den Koordinaten der linken oberen
Ecke, sowie Breite und Höhe, jeweils als int.
Wie sieht die Datendeklaration in der Klasse Rectangle aus?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-92
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Rectangle
Ein Rectangle besteht aus den Koordinaten der linken oberen
Ecke, sowie Breite und Höhe, jeweils als int.
Wie sieht die Datendeklaration in der Klasse Rectangle aus?
Antwort
public class Rectangle {
// Deklaration der Instanzvariablen:
public int x;
public int y;
public int width;
public int height;
}
...
Für Instanzvariablen sollte public eigentlich vermieden werden!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-92
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Methodendefinitionen
Die Methodendefinition für einzahlen sieht so aus:
public void einzahlen(double betrag) {
kontostand = kontostand + betrag;
}
Das bedeutet im einzelnen:
Die Methode kann von außen aufgerufen werden: public
Der Aufruf erfolgt mit einem Parameter des Typs double
⇒ Methoden dürfen beliebig viele Parameter deklarieren.
Dieser Aufruf hier liefert keinen Wert zurück: void
⇒ Eine Methode kann maximal einen Wert zurückgeben.
Aufruf führt die Statements zwischen { } aus.
Im Beispiel hier also: kontostand = kontostand + betrag;
⇒ Dabei kann neben den Parametern auch auf alle
Instanzvariablen der Klasse zugegriffen werden!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-93
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel Methodendefinition
Die Methode abheben definiert man analog durch
public void abheben(double betrag) {
kontostand = kontostand - betrag;
}
Diese Methode darf also von jedem aufgerufen werden;
liefert keinen Ergebniswert zurück;
sondern reduziert den Wert der Instanzvariable kontostand um
den Parameter betrag, dessen Wert beim Aufruf der Methode
festgelegt wird.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-94
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Rectangle
Ein Objekt der Klasse Rectangle kann man so verschieben:
r.translate(34,12)
Wie sieht die Definition der Methode translate aus?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-95
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Rectangle
Ein Objekt der Klasse Rectangle kann man so verschieben:
r.translate(34,12)
Wie sieht die Definition der Methode translate aus?
Und wie sieht der Methodenrumpf (method body) aus?
Antwort
public void translate(int dx, int dy) {
// Rumpf der Methode,
// also Abfolge von Statements
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-95
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Rectangle
Ein Objekt der Klasse Rectangle kann man so verschieben:
r.translate(34,12)
Wie sieht die Definition der Methode translate aus?
Und wie sieht der Methodenrumpf (method body) aus?
Antwort
public void translate(int dx, int dy) {
x = x + dx;
y = y + dy;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-95
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Rückgabewerte
Die Methode getKontostand liefert einen Double-Wert zurück:
public double getKontostand() {
return kontostand;
}
Die Deklaration public double getKontostand() besagt, dass
diese Methode keine Parameter erwartet, aber einen Wert des Typs
double zurückliefert.
Methoden mit Rückgabewert dürfen auch Parameter haben.
Das return Statement legt den zurückgegebenen Wert fest.
Das return Statement beendet den Methodenaufruf.
Es muss immer als letztes Statement folgen,
falls der Rückgabewert ungleich void ist.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-96
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Das return-Statement
Das return-Statement hat die Form:
return e;
wobei e ein Ausdruck ist.
Der Ausdruck e muss als Typ den Ergebnistyp der Methode, in der
das return Statement vorkommt, haben.
Gelangt der Kontrollfluss (vulgo “der Computer”) an das
Statement return e; so wird e ausgewertet und die
Methodenabarbeitung beendet. Rückgabewert der Methode ist
dann der Wert von e.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-97
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Rectangle.union
Wie würden wir die Methode union der Klasse Rectangle
deklarieren, die das kleinste achsenparallele Rechteck ausgibt,
welches das gegebene Rechteck und ein weiteres noch umschliesst?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-98
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Rectangle.union
Wie würden wir die Methode union der Klasse Rectangle
deklarieren, die das kleinste achsenparallele Rechteck ausgibt,
welches das gegebene Rechteck und ein weiteres noch umschliesst?
Antwort
public Rectangle union(Rectangle r){
// ... Rumpf der Methode ...
}
Wie würden wir den Rumpf dieser Methode implementieren?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-98
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Rectangle.union
Rectangle union(Rectangle r) {
int resx, resy, reswidth, resheight;
if (x <= r.x) resx = x; else resx = r.x;
if (y <= r.y) resy = y; else resy = r.y;
if (x + width >= r.x + r.width)
reswidth = x + width - resx;
else reswidth = r.x + r.width - resx;
if (y + height >= r.y + r.height)
resheight = y + height - resy;
else resheight = r.y + r.height - resy;
return new Rectangle(resx, resy, reswidth, resheight);
}
Beachte:
Im Methodenrumpf darf man jederzeit lokale Variablen
deklarieren.
Bei Objekterzeugung das new nicht vergessen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-99
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Rectangle.union
Rectangle union(Rectangle r) {
int resx, resy, reswidth, resheight;
if (x <= r.x) resx = x; else resx = r.x;
if (y <= r.y) resy = y; else resy = r.y;
if (x + width >= r.x + r.width)
reswidth = x + width - resx;
else reswidth = r.x + r.width - resx;
if (y + height >= r.y + r.height)
resheight = y + height - resy;
else resheight = r.y + r.height - resy;
Rectangle res = new Rectangle(resx, resy, reswidth, resheight);
return res;
}
Beachte:
Im Methodenrumpf darf man jederzeit lokale Variablen
deklarieren.
Bei Objekterzeugung das new nicht vergessen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-99
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Drei Arten von Variablen
Es gibt drei Arten von Variablen:
Lokale Variablen
(z.B. resheight, resx)
(z.B. r)
Parameter
Instanzvariablen
(z.B. height, x)
Jede Variable wird irgendwann mit ihrem Typ deklariert.
Jede Variable hat während der Abarbeitung des Programms eine
Lebensspanne, während der sie im Speicher repräsentiert ist.
Während dieser Zeit kann auf ihren Wert zugegriffen werden und
ihr Wert verändert werden (z.B. mit Zuweisung =).
Jede Variable hat auch einen Sichtbarkeitsbereich im Programm.
Man darf sie nur innerhalb ihres Sichtbarkeitsbereichs verwenden.
Die Sichtbarkeitsbereiche stellen sicher, dass beim Ablauf des
Programms nie versucht wird, auf eine Variable außerhalb ihrer
Lebensspanne zuzugreifen.
Sichtbarkeit ⊆ Lebensspanne
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-100
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Lokale Variablen
Lokale Variablen werden innerhalb eines Blocks (Methodenrumpf,
Block-Statement) deklariert und müssen explizit initialisiert werden.
Beispiel:
{
...
String name;
name = "Steffen";
...
}
// Deklaration
// Initialisierung
Lebensspanne Beginnt bei Abarbeitung ihrer Deklaration;
ihre Lebensspanne endet mit dem Verlassen des Blocks in dem
sie deklariert wurden.
Initialisierung Immer explizite Zuweisung mit =
Sichtbarkeit Lokale Variablen sind nur in ihrem Block sichtbar.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-101
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Lokale Variablen
Lokale Variablen werden innerhalb eines Blocks (Methodenrumpf,
Block-Statement) deklariert und müssen explizit initialisiert werden.
Beispiel:
{
...
String name = "Steffen"; // Dekl. mit Init.
}
...
Lebensspanne Beginnt bei Abarbeitung ihrer Deklaration;
ihre Lebensspanne endet mit dem Verlassen des Blocks in dem
sie deklariert wurden.
Initialisierung Immer explizite Zuweisung mit =
Sichtbarkeit Lokale Variablen sind nur in ihrem Block sichtbar.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-101
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Parameter
Parameter werden in der Parameterliste einer Methodendefinition
deklariert.
Beispiel:
public void abheben(double betrag) {...}
Lebensspanne
Lebensspanne beginnt mit jedem Aufruf ihrer Methode und
endet sofort mit jedem Verlassen der Methode.
Initialisierung
Bei jedem Aufruf ihrer Methode mit den konkreten Argumenten.
Sichtbarkeit
Nur sichtbar im Rumpf ihrer Methode.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-102
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Instanzvariablen
Instanzvariablen werden in einer Klasse deklariert.
Beispiel
public class MyClass {
private Rectangle rechteck;
private int x = 42; //explizit initalisiert
...
}
Lebensspanne Mit jedem durch new erzeugten Objekt der
Klasse wird ein neuer Satz aller Instanzvariablen der Klasse ins
Leben gerufen. Die Lebensspanne ist identisch zur Lebensspanne
des Objekts, welches sie enthält.
Initialisierung Immer bei der Erzeugung des umschliessenden
Objekts: explizit, per Default oder gemäß Konstruktor.
Sichtbarkeit Sichtbar in allen Methodenrümpfen ihrer Klasse.
public Instanzvariablen sind auch ausserhalb sichtbar.
Ausnahme: Überschattung durch gleichlautende Variablen
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-103
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Überschattung
Lokale Variablen und Parameter dürfen Instanzvariablen mit
gleichem Namen überschatten:
public class ShadowDemo {
double betrag;
}
public void abheben(double input) {
...
int betrag = 42; //(*)
...
}
Vor der mit (*) kommentierten Zeile ist die Instanzvariable betrag
des Typs double sichtbar; danach ist diese überschattet.
Stattdessen ist ab dann die davon unabhängige lokale Variable
betrag des Typs int sichtbar. Der Typ darf auch gleich bleiben.
Überschattung ohne guten Grund sollte man vermeiden.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-104
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Temporäre Überschattung
public class ShadowDemo {
double betrag;
public void abheben(double input) {
...
// (A)
if (betrag > 0) {
...
// (B)
String betrag = "42";
...
// (C)
}
...
// (D)
} }
Die Überschattung wirkt nur in (C), der Lebensspanne der
lokalen Variablen betrag des Typs String.
In Abschnitt (D) hat betrag wieder den Wert des Typs
double, der am Ende von Abschnitt (B) galt.
Sichtbarkeit und Lebensspanne müssen nicht identisch sein!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-105
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Impliziter Parameter this
In Rumpf einer Methode ist der implizite Parameter this
sichtbar. Er zeigt auf das Objekt, dessen Methode aufgerufen
wurde. Also ist der Typ von this immer die Klasse, in der die
Methode definiert ist.
Ausnahme: statische Methoden (später)
Das ist unter anderem auch nützlich, wenn eine lokale Variable oder
ein Parameter eine Instanzvariable desselben Namens überschattet:
public double getKontostand() {
String kontostand = "Abrakadabra";
return this.kontostand; // Zugriff Instanzvariable
}
Man kann auch immer this schreiben, um auf Instanzvariablen
zuzugreifen, um versehentliche Verwechslungen mit lokalen
Variablen oder Parametern zu verhindern.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-106
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Konstruktoren
Ein neues Bankkonto erzeugt man mit
new Bankkonto()
Die Instanzvariable kontostand wird automatisch mit 0
initialisiert.
Besser ist es, das Verhalten von “new Bankkonto()” selber zu
definieren. Dazu deklariert man in der Klasse ein oder mehrere
Konstruktoren. Dies sind Methoden, welche den gleichen Namen
wie die Klasse tragen. Der Rückgabewert wird nicht explizit
angegeben – dieser ist ja immer das neue Objekt!
Beispiel Konstruktor
Bankkonto() {
kontostand = 0.0;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-107
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Konstruktoren
Man kann auch noch zusätzlich schreiben:
Bankkonto(double kontostand) {
this.kontostand = kontostand;
}
b = new Bankkonto() erzeugt ein Bankkonto mit
Kontostand 0.0 erzeugt.
b = new Bankkonto(134.0) erzeugt ein Bankkonto mit
Kontostand 134.0 erzeugt.
Welcher Konstruktor zur Ausführung kommt, richtet sich nach
Anzahl und Typen der Parameter.
Das ein und derselbe Name mehrere Funktionen mit
unterschiedlichem Typen referenziert, bezeichnet man als
overloading. Dies ist auch bei normalen Methoden erlaubt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-108
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Rectangle
Ein Rectangle kann man auch so erzeugen:
Point p;
Rectangle b = new Rectangle(p);
Erzeugt Rechteck mit linker oberer Ecke p und Breite & Höhe = 0.
Wie ist das in der Klasse Rectangle definiert?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-109
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Rectangle
Ein Rectangle kann man auch so erzeugen:
Point p;
Rectangle b = new Rectangle(p);
Erzeugt Rechteck mit linker oberer Ecke p und Breite & Höhe = 0.
Wie ist das in der Klasse Rectangle definiert?
Antwort
Rectangle(Point p) {
x = p.x;
y = p.y;
width = 0;
height = 0;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-109
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Overloading Methoden
public void einzahlen(double betrag) {
kontostand = kontostand + betrag;
}
public void einzahlen(int betragEuro, int betragCent) {
kontostand = kontostand + betragEuro + betragCent / 100.0;
}
Damit kann man dann z.B. schreiben:
Bankkonto johannaSpar = new Bankkonto(100.0);
Bankkonto matthiasGiro = new Bankkonto(100.0);
johannaSpar.einzahlen(12.34);
matthiasGiro.einzahlen(39, 95);
Martin Hofmann, Steffen Jost
// =
// =
Einführung in die Programmierung
112.34
139.95
Objekte und Klassen
3-110
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Vergleichen von Objekten
Identische Objekte
Objekte kann man mit == vergleichen. Dabei werden aber nur die
Speicheradressen verglichen, es wird also nur geprüft, ob es sich
wirklich um zwei Referenzen auf dasselbe Objekt handelt.
Inhaltliche Gleichheit
Die meisten Objekte bieten eine Methode equals an, welche die
Werte der Instanzvariablen vergleicht, oder eine andere sinnvolle
semantische Gleichheit definiert.
Dies hängt von der Definition der Methode equals ab.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-111
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Objekte vergleichen
Die Klasse Point repräsentiert Punkte in der (zwei dimensionalen)
Ebene, speichert also zwei Werte des Typs int
Point p = new Point(4,5);
Point q = p;
Point r = new Point(4,5);
System.out.println(p==q);
System.out.println(q==r);
System.out.println(p.equals(q));
System.out.println(q.equals(r));
Was kommt heraus?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-112
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: Objekte vergleichen
Die Klasse Point repräsentiert Punkte in der (zwei dimensionalen)
Ebene, speichert also zwei Werte des Typs int
Point p = new Point(4,5);
Point q = p;
Point r = new Point(4,5);
System.out.println(p==q);
System.out.println(q==r);
System.out.println(p.equals(q));
System.out.println(q.equals(r));
Was kommt heraus?
Ausgabe:
true
false
true
true
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-112
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Der spezielle Wert null
Jede Klasse definiert einen Typ von Objekten (Bankkonto, String,
usw.). Ein Typ ist eine Menge von Werten.
Werte eines Objekttyps sind. . .
Verweise auf (Speicheradressen von) Objekten der
entsprechenden Klasse; oder
der spezielle Wert null.
Ist der Wert eines Objektausdrucks null, so führen darauf
ausgeführte Methodenaufrufe zu Laufzeitfehlern!
Alle Variablen von Objekttypen werden mit null initalisiert, falls
keine explizite Initialisierung angegeben wurde.
Mit der Operationen == kann man testen, ob eine Variable eines
Objekttyps gleich null ist.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-113
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: null-Test
public void ueberweisen(double betrag, Bankkonto empfaenger) {
kontostand = kontostand - betrag;
if (empfaenger == null)
/* Fehlerbehandlung */
else
empfaenger.einzahlen(betrag);
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-114
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: null-Test
public void ueberweisen(double betrag, Bankkonto empfaenger) {
kontostand = kontostand - betrag;
if (empfaenger == null)
/* Fehlerbehandlung */
else
empfaenger.einzahlen(betrag);
}
Achtung Seiteneffekt
Diese Methode verändert die Instanzvariable von einem anderem
Bankkonto-Objekt!
Normalerweise sollte eine Methode nur das Objekt verändern, mit
dem die Methode aufgerufen wurde, also this
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-114
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Seiteneffekte
Verändert eine Methode die Instanzvariablen von anderen Objekten
als this so spicht man in Java von einem Seiteneffekt.
Beispiel: Methode ueberweisen verändert die Instanzvariable des
zweiten Parameters.
Seiteneffekte sollten so wenig wie möglich verwendet werden, und
auf jeden Fall nur da, wo es wirklich sinnvoll ist.
Seiteneffekte erschweren die Lesbarkeit/Verständlichkeit eines
Programmes, da man das Vorhandensein von Seiteneffekten nicht
an der Signatur der Methode erkennen kann.
Man muss darauf hoffen, dass ein Programmierer alle
möglicherweise auftretenden Seiteneffekte in der Dokumentation
erwähnt.
Good Luck! ;)
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-115
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Seiteneffekte
Es gibt daher auch Programmieransätze, welche Seiteneffekte
weitestgehend verbieten möchten.
siehe ProMo, Semester 2
Man kann auch sagen, dass eine simple Zuweisung wie z.B. x=x+1
den Seiteneffekt hat, die Variable x zu verändern.
⇒ Alle Zustand-verändernden Statements haben einen Seiteneffekt!
Lehnt man dies ab, so werden Variablen also nur noch initialisiert,
aber danach niemals mehr verändert – wie in der Mathematik!
Dies hat unter Anderem den Vorteil, dass Programme lokal
verständlich werden, da man nicht mehr an einen Zustand denken
muss.
Auch in Java ist inzwischen die Ansicht weit verbreitet, dass
Instanzvariablen möglichst nicht verändert werden sollen, sondern
stattdessen neue Objekte zurückgegeben werden sollen.
Java Kompiler ist inzwischen darauf optimiert worden
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-116
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Seiteneffekte
Auch Ein-/Ausgabe (engl. I/O), also z.B. Lesen/Schreiben von
Dateien, Netzwerkverbindungen, Konsole (also Tastatureingaben
und Bildschirmausgaben) sind Seiteneffekte!
Auch deren Verwendung innerhalb von Methoden sollte möglichst
minimiert werden.
Es ist z.B. oft sinnvoll, alle Konsolenoperationen an einer zentralen
Stelle im Programm zu bündeln.
In Sprachen, welche Seiteneffekte verbieten, tut man sich mit
notwendigem I/O entsprechend schwerer.
In Java haben wir die Freiheit, selbst zu entscheiden, wo wir
Seiteneffekt für angemessen halten.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-117
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Statische Methoden
Methodendefinition können den Qualifikator static tragen. Diese
Methoden haben keinen Zugriff auf die Instanzvariablen und keinen
impliziten Parameter this.
Dafür kann man die Methode ohne Bezug auf ein bestimmtes
Objekt aufrufen. Stattdessen gibt man den Klassennamen an.
Beispiel: statische Methoden
int i = Math.max(42,7);
max ist eine statische Methode der Klasse Math
Statische Methoden benutzt man z.B. um mathematische
Funktionen zu realisieren (auch Prozeduren in Pascal, C, etc.)
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-118
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel
Man könnte in die Klasse Bankkonto eine statische Methode
markNachEuro schreiben, die DM-Beträge in e konvertiert:
public static double markNachEuro(double markBetrag) {
return markBetrag / 1.9558;
}
Man kann dann zum Beispiel schreiben:
johannaSpar.einzahlen(Bankkonto.markNachEuro(500.0));
Achtung:
Man kann natürlich nicht schreiben Bankkonto.einzahlen(...)
Denn welchen Wert sollte denn dann kontostand haben?
johannaSpar.markNachEuro(...) ist zwar erlaubt, aber komisch.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-119
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Statische “Bibliotheksklassen”
Manche Klassen besitzen nur statische Methoden und haben dann
keine Instanzvariablen. Solche Klassen dienen einfach der
Gruppierung verwandter Operationen.
Beispiel
Wir könnten eine Klasse Numeric schreiben, welche numerische
Verfahren enthält und insbesondere eine statische Methode für
ungefähre Gleichheit.
public static boolean approxEqual(double x, double y) {
final double EPSILON = 1E-12;
return Math.abs(x-y) <= EPSILON;
}
Die Klasse Math ist ein Beispiel einer solchen statischen Klasse.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-120
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Statische Variablen
Auch die Deklarationen von “Instanzvariablen” in einer Klasse
können mit dem Qualifikator static versehen werden.
Solch eine Variable ist dann für die gesamte Klasse nur einmal
vorhanden. Alle Objekte der Klasse können diese eine Variable lesen
und verändern.
Man bezeichnet solch eine Variable als Klassenvariable oder
statische Variable.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-121
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Beispiel: statische Klassenvariable
public class Bankkonto {
/** Der Kontostand. */
private double kontostand;
/** Der Wert eines EUR in USD. */
private static double dollarkurs;
/** Setzen von {@link #dollarkurs dollarkurs} */
public static void setDollarkurs(double kurs) {
dollarkurs = kurs;
}
/** Abfragen des Kontostands in USD. @return den
Kontostand in USD gemaess @see dollarkurs. */
public double getbalanceinUSD() {
return kontostand * dollarkurs;
}
Javadoc erzeugen mit javadoc -author -version -private
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-122
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Methoden unserer Klasse Bankkonto
public void einzahlen(double betrag) {
kontostand = kontostand + betrag;
}
public void abheben(double betrag) {
kontostand = kontostand - betrag;
}
public boolean equals(Bankkonto konto) {
return kontostand == konto.getKontostand();
}
public double getKontostand() {
return kontostand;
}
boolean istUeberzogen() {
return kontostand < 0.0;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-123
Objekte und Klassen
Instanzvariablen Methoden Variablen Konstruktoren null Seiteneffekte static
Überweisen
public void ueberweisen(double betrag, Bankkonto empfaenger) {
kontostand = kontostand - betrag;
empfaenger.einzahlen(betrag);
}
auch richtig, evtl. sogar besser:
public void ueberweisen(double betrag, Bankkonto empfaenger) {
this.abheben(betrag);
empfaenger.einzahlen(betrag);
}
Bei Methodenaufrufe mit this kann man es auch weglassen, wenn
man will:
abheben(betrag);
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-124
Zfg. Klassen & Objekte
Instanzvariablen Klassenvariablen Methoden Konstruktoren Klassen Objekte
Instanzvariablendeklaration
Die Deklaration einer Instanzvariablen hat die Form
private typ variablenName ;
Hier ist variablenName der Name der deklarierten Variablen; und
typ ist der Typ der Instanzvariablen.
Der Typ kann sein:
ein Basistyp, z.B. double, int, char, boolean,. . .
eine vordefinierte Klasse, z.B. String, Point, Rectangle, . . .
eine selbstdefinierte Klasse, z.B. Bankkonto, Dreieck, . . .
Instanzvariablen dürfen nur Werte dieses Typs enthalten und dürfen
nur mit Operationen dieses Typs bearbeitet werden.
Beispiel:
Man darf x.length() schreiben, falls x vom Typ String ist.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-125
Zfg. Klassen & Objekte
Instanzvariablen Klassenvariablen Methoden Konstruktoren Klassen Objekte
Klassenvariablendeklaration
Die Deklaration einer Klassenvariable hat die Form
private static typ variablenName ;
Merkmale Klassenvariable:
Alle Objekte dieser Klasse greifen auf die gleiche Variable zu!
Vorsicht vor Seiteneffekte!
Darf auch in statischen Methoden verwendet werden.
Merkmale Instanzvariable:
Jedes Objekte trägt seinen eigen Satz aller Instanzvariablen.
Dürfen nicht in statischen Methoden verwendet werden – sind
ja nur in Objekten gespeichert
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-126
Zfg. Klassen & Objekte
Instanzvariablen Klassenvariablen Methoden Konstruktoren Klassen Objekte
Methodendefinition
public ergTyp methName ( typ1 par1 , . . . typn parn ) {
methRumpf
}
methName ist der Name der Methode. Ist e ein Objekt der
Klasse, dann ruft man die Methode so auf:
e.methName( . . . )
Die Variablen par1 , . . . , parn sind die Parameter. Beim
Methodenaufruf muss man konkrete Werte für die Parameter
bereitstellen.
Die Typen typ1 ,. . . ,typn sind die Typen der Parameter
(Basistypen oder Klassen).
Beim Methodenaufruf müssen die konkreten Parameter den
angekündigten Typ haben!
Der Typ ergTyp ist der Typ des Ergebnisses des
Methodenaufrufs.
Ist er void, so wird kein Ergebnis zurückgegeben.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-127
Zfg. Klassen & Objekte
Instanzvariablen Klassenvariablen Methoden Konstruktoren Klassen Objekte
Der Methodenrumpf
Der Methodenrumpf kommt zur Ausführung, wenn die
Methode bei einem Objekt der Klasse aufgerufen wird.
Im Methodenrumpf sind die Instanzvariablen, sowie die
Parameter verfügbar. Außerdem möglicherweise deklarierte
lokale Variablen.
Der aktuelle Wert der Instanzvariablen ist bestimmt durch das
Objekt, bei dem die Methode aufgerufen wird.
Der aktuelle Wert der Parameter ergibt sich aus den konkreten
Parametern, mit denen die Methode aufgerufen wird.
Man kann sagen, dass die Instanzvariablen Teile des impliziten
Parameters this sind.
Hat die Methode einen von void verschiedenen Ergebnistyp,
so muss der Rumpf ein entsprechendes return-Statement
enthalten.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-128
Zfg. Klassen & Objekte
Instanzvariablen Klassenvariablen Methoden Konstruktoren Klassen Objekte
Konstruktoren Definition
Eine Konstruktordefinition hat die Form
NameDerKlasse ( typ1 par1 , typ2 par2 , . . . typn parn ) {
konstruktorRumpf
}
der Konstruktor wird aufgerufen in der Form
new NameDerKlasse(e1 , . . . ,en )
Dies ist ein Ausdruck, dessen Wert ein frisches Objekt der Klasse
nameDerKlasse ist.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-129
Zfg. Klassen & Objekte
Instanzvariablen Klassenvariablen Methoden Konstruktoren Klassen Objekte
Konstruktorauswertung
Die Instanzvariablen des neu erzeugten Objekts werden wie folgt
initialisiert:
Zunächst werden sie mit den Defaultwerten (0 für Zahlen,
null für Objekte) besetzt.
Dann werden die Ausdrücke e1 , . . . , en ausgewertet und ihre
Werte den Parametern par1 , . . . , parn des Konstruktors
zugewiesen.
Wie immer müssen die Typen stimmen.
Schließlich wird der Block konstruktorRumpf ausgeführt.
Typischerweise hat das eine Zuweisung zu den Instanzvariablen
in Abhängigkeit von den Parametern zur Folge.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-130
Zfg. Klassen & Objekte
Instanzvariablen Klassenvariablen Methoden Konstruktoren Klassen Objekte
Beispiel: Konstruktorauswertung
Gegeben sei folgende Konstruktordefinition:
Bankkonto(double betrag) {
kontostand = betrag + 5.0;
}
Der Ausdruck new Bankkonto(e) hat als Wert einen Verweis auf
ein neues Objekt der Klasse Bankkonto, dessen Instanzvariable
kontostand initialisiert wurde mit dem Wert des Ausdrucks e plus
5.0
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-131
Zfg. Klassen & Objekte
Instanzvariablen Klassenvariablen Methoden Konstruktoren Klassen Objekte
Zusammenfassung Klassen
Klassen beinhalten Deklaration von Instanzvariablen,
Methoden, Konstruktoren.
Klassen dienen der Definition gleichartiger Objekte
Von einer Klasse gibt es im allgemeinen mehrere Objekte
Konstruktoren definieren die Initialisierung der Instanzvariablen
bei der Erzeugung eines neuen Objektes der Klasse.
Jede Variable hat eine Lebensspanne und einen
Sichtbarkeitsbereich im Programmtext.
Die vier Arten der Variablen sind: Instanzvariablen,
Klassenvariablen, Parameter und lokale Variablen.
Overloading bezeichnet das Vorhandensein unterschiedlicher
Methoden und Konstruktoren desselben Namens
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-132
Zfg. Klassen & Objekte
Instanzvariablen Klassenvariablen Methoden Konstruktoren Klassen Objekte
Zusammenfassung Objekte
Ein Objekt hat seine Klasse als Typ
Die Werte eines Klassen-Typs umfasst neben ihren Objekten
auch den speziellen Wert null.
Verweise auf Objekte werden mit == auf Identität verglichen
(“dasselbe Objekt”)
Um Objekte anhand ihrer Instanzvariablen zu vergleichen, kann
man eine equals Methode definieren.
Jedes Objekte speichert seine eigenen Instanzvariablen
Alle Objekte einer Klasse teilen sich alle statischen
Klassenvariablen der Klasse
Methoden werden von einem Objekt aus aufgerufen;
im Rumpf der Methode is dieses Objekt mit this ansprechbar
Statische Methoden (static) werden dagegen über den
Klassennamen angesprochen
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-133
UML
UML-Notation
UML = Unified Modelling Language.
ISO Standard zur grafischen Veranschaulichung von Vorgängen bei
der Softwareentwicklung:
Objektdiagramme: visualisieren Speicherinhalt
Klassendiagramme: visualisieren Klassen und ihre Beziehungen
Use-case Diagramme: visualisieren Verwendungsszenarien
Statecharts: visualisieren Zustandsübergänge
...
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-134
UML
Klassen- und Objektdiagramme
Objekte als zweigeteilte Rechtecke:
Unterstrichener Klassenname, alle Instanzvariablen mit Wert
Pfeile mit ausgefüllter Dreiecksspitze −I für Verweise.
Beispiele: siehe Folien 1-27 und 1-28
Klassen als dreigeteilte Rechtecke:
Klassenname, alle public Instanzvariablen und Methoden
“has-a”-Beziehung (Aggregation) als Pfeil mit Rautenspitze
Realisierung in Java durch Instanzvariablen
“uses”-Beziehung als gestrichelter Pfeil 99K oder auch nur
gestrichelte Verbindung. Realisierung in Java auf verschiedene
Arten: ruft Methode auf, has-a, erzeugt, . . .
“is-a”-Beziehung als Pfeil mit hohler Dreiecksspitze −B
Realisierung in Java mit später behandelter Vererbung
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-135
UML
UML-Klassendiagramm
Rectangle
Fenster
translate
union
Bankkonto
getKontostand
einzahlen
abheben
ueberweisen
...
Sparkonto
Girokonto
verzinsen
setZinssatz
getZinssatz
Browser
gebuehren
Bausparkonto
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-136
UML
Zusammenfassung UML
UML Diagramme dienen der grafischen Veranschaulichung des
Softwareentwicklungsprozesses.
UML-Klassendiagramme veranschaulichen Klassen und ihre
Beziehungen
UML-Objektdiagramme veranschaulichen den Zustand des
Speichers.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objekte und Klassen
3-137
Einführung in die Programmierung
mit Java
Teil 4: Iteration
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
3. November 2015
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-138
Inhalt Teil 4: Iteration
13
Schleifen
Die while Schleife
Schleifeninvarianten
Hoare Logik
Die for Schleife
Die do-while Schleife
Beispiele
Zusammenfassung Schleifen
14
Ein- und ausgabe mit Konsole
Dialogfenster
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-139
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Die While-Schleife
BNF-Beschreibung:
hstatementi ::= . . .
| while(hexpressioni)hstatementi
...
Ausführung von while(e)c:
Das Statement c wird solange immer wieder ausgeführt, bis der
Ausdruck e den Wert false hat.
Der Ausdruck e muss vom Typ boolean sein.
Der Ausdruck e wird vor jeder Ausführung von c ausgewertet.
Ist er schon zu Beginn false, so wird c überhaupt nicht
ausgeführt.
Bleibt er immer true, so wird c immer wieder ausgeführt.
Das Programm befindet sich dann in einer Endlosschleife.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-140
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beispiel: Zins und Zinseszins
Ein Cent werde zu 3% p.a. angelegt.
Nach wieviel Jahren ist das Kapital auf 100e angewachsen?
Wir müssen solange 3% dazuzählen, bis 100e erreicht sind und
gleichzeitig die Zahl der Durchläufe in einer lokalen Variablen
speichern.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-141
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beispiel: Zins und Zinseszins
Ein Cent werde zu 3% p.a. angelegt.
Nach wieviel Jahren ist das Kapital auf 100e angewachsen?
Wir müssen solange 3% dazuzählen, bis 100e erreicht sind und
gleichzeitig die Zahl der Durchläufe in einer lokalen Variablen
speichern.
Lösung
public class Investment {
public static void main(String[] args) {
double kapital = 0.01;
int years = 0;
while (kapital < 100.) {
years = years + 1;
kapital = kapital * 1.03;
}
System.out.println("Es dauert " + years + " Jahre");
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-141
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Schnelles Potenzieren
Folgendes Programmstück berechnet die Potenz an uns speichert
das Ergebnis in Variable r:
double r = 1;
double b = a;
int i = n;
while (i > 0) {
if (i % 2 == 1) {
r = r * b;
}
b = b * b;
i = i / 2;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-142
Schleifen
Beispiel 3
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
5
double r = 1; double b = a; int i = n;
while (i > 0) { if (i % 2 == 1) { r = r * b; }
b = b * b;
i = i / 2; }
nach Schleifendurchlauf
0
1
2
3
r
1
3
3
243
b
3
9
81
6561
i
5
2
1
0
Für höhere Exponenten werden weniger Multiplikationen benötigt:
Berechnung a32 braucht anstatt 32 nur noch 6 Multiplikationen.
Diese Berechnungsmethode ist also “schneller”!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-143
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Begründung
Wie funktioniert das ?
Es basiert auf den mathematischen Gleichungen
x 2i+1 = x 2i x
und
x 2i = (x i )2
Wie kann man formal beweisen, dass der Algorithmus immer
korrekt funktioniert?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-144
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Invariante
Beweis (1/3)
Wir bezeichnen mit I die Eigenschaft, dass r · bi = an gilt.
Die Eigenschaft I gilt trivialerweise zu Beginn der Schleife, denn
dort gilt r = 1, b = a, n = i.
Gilt sie vor Ausführung des Schleifenrumpfes, so gilt sie auch
danach.
Beweis dieses Teils auf folgenden Folien
Somit gilt sie auch bei Verlassen der Schleife.
Da dann i = 0 gilt, folgt r = an wie benötigt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-145
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beweis (2/3)
Es bezeichne ralt , rneu den Wert von r vor und nach dem
Schleifenrumpf. Ebenso für Variablen b und i.
Wir unterscheiden nun zwei Fälle, und betrachten diese einzeln.
Falls ialt gerade ist, so gilt dem Code entsprechend:
bneu = b2alt
ineu = ialt /2
rneu = ralt
ialt
neu = r
Somit gilt in diesem Fall rneu bineu
alt balt wie benötigt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-146
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beweis (3/3)
Falls ialt ungerade ist, so gilt:
bneu = b2alt
ineu = (ialt − 1)/2
rneu = ralt · balt
Wir rechnen:
2ineu
ineu = r
alt −1
alt
rneu bneu
= ralt · balt · bialt
= ralt bialt
.
alt · balt · balt
Damit haben wir nun auch die Behauptung am Anfang des
Beweises gezeigt.
Die Eigenschaft I heißt Invariante.
Sie gilt unverändert vor und nach jedem Schleifendurchlauf.
Es bietet sich oft an, bei Schleifen nach geeigneten Invarianten zu
suchen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-147
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Hoare Logik
Um die
von
imperativera Software formell zu beweisen,
“There
areKorrektheit
two ways of
constructing
entwickelte
der One
britische
software
design:
way Informatiker
is to make C.A.R. Hoare um 1969
zur
Systematisierung
die
Logik.
it so simple that there areHoare
obviously
no deficiencies, and the other way
Dies ist ein Menge von logischen
is to make it so complicated that
Regeln, die wir direkt an einem
there are no obvious deficiencies.
gegebenen, imperativen Quellcode
The first method is far more difficult.”
anwenden können, um mathematische
during 1980 ACM Turing Award Lecture
Aussagen darüber zu treffen.
Sir C.A.R. “Tony” Hoare (*1934)
ist unter Anderem noch bekannt
für den Quicksort-Algorithmus
und den Null-Pointer.
Martin Hofmann, Steffen Jost
C.A.R. Hoare
Quelle: Wikimedia Commons
Rama, Cc-by-sa-2.0-fr
Einführung in die Programmierung
Iteration
4-148
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Hoare Logik
Um die
von
imperativera Software formell zu beweisen,
“There
areKorrektheit
two ways of
constructing
entwickelte
der One
britische
software
design:
way Informatiker
is to make C.A.R. Hoare um 1969
zur
Systematisierung
die
Logik.
it so simple that there areHoare
obviously
no deficiencies, and the other way
Dies ist ein Menge von logischen
is to make it so complicated that
Regeln, die wir direkt an einem
there are no obvious deficiencies.
gegebenen, imperativen Quellcode
The first method is far more difficult.”
anwenden können, um mathematische
during 1980 ACM Turing Award Lecture
Aussagen darüber zu treffen.
Sir C.A.R. “Tony” Hoare (*1934)
ist unter Anderem noch bekannt
für den Quicksort-Algorithmus
und den Null-Pointer.
Martin Hofmann, Steffen Jost
C.A.R. Hoare
Quelle: Wikimedia Commons
Rama, Cc-by-sa-2.0-fr
Einführung in die Programmierung
Iteration
4-148
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Hoare Logik
Ein Hoare-Tripel ist ein formaler Ausdruck der Form
{P}c{Q}
wobei P, Q Aussagen über den Programmzustand sind (Werte von
Variablen, Aussehen des Speichers) und c ein Statement ist.
Definition
Ein solches Hoare-Tripel ist gültig, wenn für jeden
Programmzustand q, der die Vorbedingung P erfüllt, gilt:
Falls die Abarbeitung von c ausgehend von Zustand q terminiert
mit einem Folgezustand q 0 , so muss dieser Folgezustand q 0 die
Nachbedingung Q erfüllen.
P, Q werden auch als Zusicherungen bezeichnet.
Wichtig: Für unsere Zwecke sind “Aussagen” deutsche oder
englische Sätze, die informelle Mathematik- und Logiknotation
verwenden dürfen. Für formale Programmverifikation benötigt man
eine formalisierte Zusicherungssprache.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-149
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Logiknotation
Mathematik
Natürliche Sprache
Java
¬A
“nicht A”
!
A∧B
“A und B”
&&
A∨B
“A oder B”
||
A→B
“A impliziert B”
Warheitstafel
¬
T F
F T
∧ T F
T T F
F F F
∨ T F
T T T
F T F
→ T F
A =T T F
F T T
Die Aussage A → B ist äquivalent zu ¬A ∨ B
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-150
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Hoare’sche Regeln
Es gibt nun eine Menge von (syntaxgerichteten) Regeln, die es
gestatten, die gültigen Hoare-Tripel formal herzuleiten.
Die Regeln haben immer die Form:
P1 · · · Pn
K
Dabei sind P1 , . . . , Pn die Prämissen und K die Konklusion der
Regel. Meist sind dies Hoare-Tripel oder andere “Aussagen”.
Die Reihenfolge der Prämissen ist dabei unerheblich.
Wenn wir die Gültigkeit aller Prämissen zeigen können, dann gibt
uns die Anwendung einer passenden Hoare-Regel die Gültigkeit der
Konklusion.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-151
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Regel für While-Schleifen
P→I
{I ∧ b}c{I }
I ∧ ¬b → Q
{P}while(b)c{Q}
Um zu zeigen, dass {P}while(b)c{Q} gültig ist, muss eine
geeignete Aussage I , die Invariante, gefunden werden, derart dass,
1
2
3
P impliziert I (für beliebigen Zustand)
{I ∧ b}c{I } ist gültiges Hoare-Tripel
(ggf. vermöge anderer Hoare’scher Regeln)
I ∧ ¬b impliziert Q. D.h. jeder Zustand q, der I erfüllt und in
dem b den Wert false hat, erfüllt die Zusicherung Q.
Zur Vereinfachung setzen wir voraus, dass die Auswertung der
Bedingung b keine Seiteneffekte hat. Kommt die Bedingung in
einer Zusicherung vor, so ist der Wahrheitswert der Bedingung in
dem Zustand, auf den sich die Zusicherung bezieht, gemeint.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-152
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beispiel
{x = c ∧ y = d } while(x>0){y=y+1; x=x-1;} {y ≥ c + d }
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-153
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beispiel
x=c ∧y=d → I
{I ∧ x > 0} {y=y+1; x=x-1;} {I }
I ∧x≤0 → y ≥c +d
{x = c ∧ y = d } while(x>0){y=y+1; x=x-1;} {y ≥ c + d }
Welche Invariante?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-153
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beispiel
x=c ∧y=d → I
{I ∧ x > 0} {y=y+1; x=x-1;} {I }
I ∧x≤0 → y ≥c +d
{x = c ∧ y = d } while(x>0){y=y+1; x=x-1;} {y ≥ c + d }
Welche Invariante?
Einsetzen von x + y = c + d für Invariante I erfüllt alle hier
geforderten Aussagen:
(x = c ∧ y = d )
→
(x + y = c + d )
(x + y = c + d ) ∧ x ≤ 0
→
y ≥c +d
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-153
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beispiel
x=c ∧y=d → I
{I ∧ x > 0} {y=y+1; x=x-1;} {I }
I ∧x≤0 → y ≥c +d
{x = c ∧ y = d } while(x>0){y=y+1; x=x-1;} {y ≥ c + d }
Welche Invariante?
Einsetzen von x + y = c + d für Invariante I erfüllt alle hier
geforderten Aussagen:
(x = c ∧ y = d )
→
(x + y = c + d )
(x + y = c + d ) ∧ x ≤ 0
→
y ≥c +d
Es verbleibt zu zeigen:
{x + y = c + d ∧ x > 0} {y=y+1; x=x-1;} {x + y = c + d }
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-153
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Regel für die Hintereinanderausführung
{P}c1 {R}
{R}c2 {Q}
{P}c1 ;c2 {Q}
Hier bezeichnet c1 ;c2 die Hintereinanderausführung von c1 , c2 .
Für Java müsste man eigentlich {c1 c2 } anstatt c1 ;c2 schreiben,
aber zu viele geschweifte Klammern verwirren dann.
Auch die Verallgemeinerung auf mehr als zwei aufeinanderfolgende
Statements belassen wir implizit: Für den Block {c1 c2 · · · cn } mit
Vorbedingung P und Nachbedingung Q benötigen wir Zusicherungen
Ri mit R0 ≡ P und Rn ≡ Q und die Hoare-Tripel {Ri−1 } ci {Ri }.
Es hat den Anschein, als müsste man auch hier die Zusicherung R
geschickt “raten”. Das ist nicht der Fall: Man wählt vielmehr R als
die schwächste Bedingung, sodass {R}c2 {Q} noch gilt.
Beispiel
Falls c2 eine Zuweisung ist, so erhält man R einfach durch
rückwärts gerichtetes einsetzen der Zuweisung in Q.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-154
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beispiel (Fortsetzung)
Es verbleibte zu zeigen:
{x + y = c + d ∧ x > 0} {y=y+1; x=x-1;} {x + y = c + d }
Anwendung der Regel:
{x + y = c + d ∧ x > 0} y=y+1; {R}
{R} x=x-1; {x + y = c + d }
{x + y = c + d ∧ x > 0} {y=y+1; x=x-1;} {x + y = c + d }
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-155
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beispiel (Fortsetzung)
Es verbleibte zu zeigen:
{x + y = c + d ∧ x > 0} {y=y+1; x=x-1;} {x + y = c + d }
Anwendung der Regel:
{x + y = c + d ∧ x > 0} y=y+1; {R}
{R} x=x-1; {x + y = c + d }
{x + y = c + d ∧ x > 0} {y=y+1; x=x-1;} {x + y = c + d }
Für R setzen wir hier x + y = c + d + 1 ein, womit es verbleibt,
noch die Gültigkeit der folgenden beiden Hoare-Tripel zu zeigen:
{x + y = c + d ∧ x > 0} y=y+1; {x + y = c + d + 1}
{x + y = c + d + 1} x=x-1; {x + y = c + d }
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-155
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Regel für die Zuweisung
Sei e ein seiteneffektfreier Ausdruck und x eine Programmvariable.
Für das Zuweisungsstatement x=e; gilt die folgende Hoare Regel:
P → Q[x := e]
{P}x=e;{Q}
Bedeutung von Q[x := e]
Für jeden Zustand q ist die Zusicherung Q genau dann erfüllt,
wenn Q[x := e] in einem Zustand q 0 erfüllt ist, wobei sich q 0 von q
nur dadurch unterscheidet, dass x den Wert e in q 0 hat.
Beispiel
Ist Q ≡ “x = 19”, also in all den Zuständen erfüllt, in denen x den
Wert 19 hat, so ist Q[x := x+1] ≡ “x + 1 = 19”, also “x = 18”.
Es gilt also z.B.: {x = 18}x=x+1;{x = 19}.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-156
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beispiel (Fortsetzung)
Es verbleibte zu zeigen:
{x + y = c + d ∧ x > 0} y=y+1; {x + y = c + d + 1}
{x + y = c + d + 1} x=x-1; {x + y = c + d }
Anwendung der Regeln:
Wenn x + y = c + d ∧ x > 0 gilt, dann gilt auch
x + (y + 1) = c + d + 1. Die Anwendung der Zuweisung erlaubt
uns dann, (y + 1) durch y zu ersetzen.
Wenn x + y = c + d + 1 gilt, dann gilt auch (x − 1) + y = c + d .
Die Anwendung der Zuweisung erlaubt uns dann, (x − 1) durch x
zu ersetzen wie benötigt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-157
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beispiel (Zusammenfassung)
{x = c ∧ y = d }
{x + y = c + d } while(x>0){
{x > 0 ∧ x + y = c + d }
{x + (y+1) = c + d + 1}
y=y+1;
{x + y = c + d + 1}
{(x-1) + y = c + d }
x=x-1;
{x + y = c + d } }
{x ≤ 0 ∧ x + y = c + d }
{y ≥ c + d }
Bemerkung:
Es bietet sich oft an, solche Beweise rückwärts aufzubauen, so dass
man immer mit der schwächsten Nachbedingung arbeitet, welche
gerade noch stark genug ist.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-158
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Konsequenz-Regel
Im behandelten Beispiel haben wir z.B. innerhalb der Schleife von
x > 0 keinen Gebrauch gemacht.
Generell ist erlaubt:
schwächere Vorbedingung zu fordern
stärkere Nachbedingungen zu garantieren
Formalierung als Regel:
P → P0
Martin Hofmann, Steffen Jost
{P 0 }c{Q 0 }
{P}c{Q}
Einführung in die Programmierung
Q0 → Q
Iteration
4-159
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Regeln für die Fallunterscheidung
Sei b ein seiteneffektfreier Ausdruck vom Typ boolean.
Es gelten die folgenden Hoare Regeln:
{P ∧ b}c1 {Q}
{P ∧ ¬b}c2 {Q}
{P}if(b) c1 else c2 {Q}
Wir müssen also beide Möglichkeiten einzelen behandeln; dabei
bekommen wir als zusätzliche Vorbedingung, dass b entsprechend
dem Fall gilt bzw. nicht gilt.
Die Regel hat eine Spezialisierung für den Fall, dass es keinen
else-Zweig gibt:
{P ∧ b}c{Q}
P ∧ ¬b → Q
{P}if(b) c{Q}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-160
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beispiel
Betrachten wir das folgende Hoare-Tripel:
{x > 5} while (x>5) { y=y+1; } {x ≤ 5}
Ist dieses Hoare-Tripel gültig?
Wenn ja, was bedeutet es?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-161
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Partielle Korrektheit
Angenommen das Hoare-Tripel {P} c {Q} wäre gültig.
Dies bedeutet:
Für jeden Programmzustand, der Prämisse P erfüllt, gilt nach
Abarbeitung von c, dass Konklusion Q gilt; falls die Abarbeitung
terminiert!
Mit den behandelten Regeln läßt sich nur partielle Korrektheit
beweisen, d.h. ein Programm arbeitet Korrektheit unter der
Voraussetzung, dass es terminiert und auch, dass keine Ausnahme
geworfen wird.
Totale Korrektheit bedeutet dagegen, dass das Programm immer
teminiert und auch das keine Fehler auftreten.
Es gibt Versionen von Hoare Logik, welche auch den Beweis der
totalen Korrektheit erlauben; diese behandeln wir hier jedoch nicht.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-162
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Weiterführendes
Man kann zeigen, dass alle gültigen Hoare-Tripel, die die
behandelten Konstrukte (while, if, Zuweisung) enthalten, auch
mit den Hoare-Regeln hergeleitet werden können.
Es gibt Hoare-Regeln für alle anderen Java Konstrukte,
insbesondere Methodenaufrufe, sowie für Ausdrücke mit
Seiteneffekten.
Es gibt Versionen der Hoare Logik, bei denen in der
Nachbedingung ein Zugriff auf die Werte der Variablen vor
Ausführung des Statements möglich ist. Dieser Effekt kann in
unserer Version nur über logische Variablen erreicht werden:
{x = A}c{x = A} drückt aus, dass c den Wert von x nicht
verändert.
Man kann diese Idee so systematisieren, dass eine Herleitung in
der Hoare Logik aus geeigneten Invarianten für die Schleifen
automatisch erzeugt werden kann.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-163
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Übersicht Hoare Logik
P→I
{I ∧ b}c{I }
I ∧ ¬b → Q
{P}while(b)c{Q}
{P}c1 {R}
{R}c2 {Q}
{P}c1 ;c2 {Q}
P → Q[x := e]
{P}x=e;{Q}
(While)
(Komposition)
(Zuweisung)
{P ∧ b}c1 {Q}
{P ∧ ¬b}c2 {Q}
{P}if(b) c1 else c2 {Q}
(If-Else)
{P ∧ b}c{Q}
P ∧ ¬b → Q
{P}if(b) c{Q}
P → P0
Martin Hofmann, Steffen Jost
{P 0 }c{Q 0 }
{P}c{Q}
Q0 → Q
Einführung in die Programmierung
(If)
(Konsequenz)
Iteration
4-164
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beispiel: Hoare Logik
In einer Vergangenen EiP-Klasur wurde folgende Aufgabe gestellt:
Geben ist folgendes Programmfragment c:
r = z;
while (r >= n) {
q = q + 1;
r = r - n;
}
Beweisen Sie die Gültigkeit des Hoare-Triple
{q = 0 ∧ n > 0} c {z = q · n + r ∧ r < n}
1
2
3
4
5
6
Was gilt direkt vor Beginn der Schleife?
Welche Invariante wählen Sie für die Schleife?
Was gilt nach Abarbeitung des Programmfragments?
Was gilt direkt vor Abarbeitung des Schleifenrumpfes?
Was gilt zwischen den Zuweisungen im Schleifenrumpf?
Was gilt direkt nach Abarbeitung des Schleifenrumpfes?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-165
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Lösung (1/3)
Zuerst behandeln wir die Zuweisung vor der Schleife, um
herauszufinden, was unmittelbar vor der Schleife gilt. Dies ist ein
gewöhnliches Hoare-Tripel für Zuweisungen:
{q = 0 ∧ n > 0} r=z; {q = 0 ∧ n > 0 ∧ r = z}
Als Invariante wählen wir dann:
z =q·n+r
Dies ist letzendlich nur die gegebene Nachbedingung (abzüglich
negierter Schleifenbedingung), welche wir ja aus der Invariante
zusammen mit der negierten Schleifenbedingung herleiten müssen
(gemäß der Hoare-Regel für While-Schleifen).
Im Allgemeinen kann es auch sein, dass die Invariante echt stärker
ist als die geforderte Nachbedingung (z.B. zusätzliche Annahmen,
welche benötigt werden um die Invariante nach Durchlauf des
Schleifenrumpfes wieder herzustellen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-166
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Lösung (2/3)
Die Invariante gilt trivialerweise vor Beginn der Schleife:
z =0·n+r
Die Invariante zusammen mit der negierten Schleifenbedingung ist
identisch zur geforderten Nachbedingung
z =q·n+r ∧r <n
denn ¬(r ≥ n) ≡ r < n.
Zur Anwendung der Hoare-Regel für While-Schleifen müssen wir
also nur noch die Gültigkeit des folgenden Hoare-Tripels beweisen:
{q · n + r ∧ r ≥ n} q=q+1;r=r-n {q · n + r }
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-167
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Lösung (3/3)
{q · n + r ∧ r ≥ n}
{(q + 1 −1) · n + r ∧ r ≥ n} q=q+1;
| {z }
q:=
{(q − 1) · n + r ∧ r ≥ n}
{q · n + r| {z
− n} ∧ r ≥ n} r=r-n;
r :=
{q · n + r ∧ r ≥ n}
{q · n + r }
Zeilen ohne Code sind Anwendungen der Konsequenz-Regel;
Zeilen mit Code sind Anwendung der Zuweisungs-Regel;
der gesamte Block folgt aus der Kompositions-Regel.
(Nachbedinungen auf rechter Seite weggelassen, da identisch zur
Vorbedingung in der Zeile darunter)
Der Beweis wurde hier von oben-nach-unten konstruiert. Sonst
hätte man das unnötige r ≥ n wohl gleich ganz oben weggworfen.
Macht aber ja keinen großen Unterschied.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-168
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Stärkste vs. Schwächste Zusicherung
Was bedeutet die Gültigkeit folgender Hoare-Tripel:
1
{true} c {Q}
2
{false} c {Q}
3
{P} c {true}
4
{P} c {false}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-169
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Stärkste vs. Schwächste Zusicherung
Was bedeutet die Gültigkeit folgender Hoare-Tripel:
1
{true} c {Q} “Wenn c terminiert, dann gilt danach Q.”
Dies gilt für jeden beliebigen Anfangszustand!
2
{false} c {Q}
3
{P} c {true}
4
{P} c {false}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-169
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Stärkste vs. Schwächste Zusicherung
Was bedeutet die Gültigkeit folgender Hoare-Tripel:
1
2
{true} c {Q} “Wenn c terminiert, dann gilt danach Q.”
Dies gilt für jeden beliebigen Anfangszustand!
{false} c {Q} Dieses Tripel ist trivialerweise stets erfüllt: Es
gibt ja keinen Zustand, der die Vorbedingung erfüllt, also muss
auch die Nachbedingung nicht gelten (sie kann aber gelten).
3
{P} c {true}
4
{P} c {false}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-169
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Stärkste vs. Schwächste Zusicherung
Was bedeutet die Gültigkeit folgender Hoare-Tripel:
1
2
3
4
{true} c {Q} “Wenn c terminiert, dann gilt danach Q.”
Dies gilt für jeden beliebigen Anfangszustand!
{false} c {Q} Dieses Tripel ist trivialerweise stets erfüllt: Es
gibt ja keinen Zustand, der die Vorbedingung erfüllt, also muss
auch die Nachbedingung nicht gelten (sie kann aber gelten).
{P} c {true} Auch dieses Tripel ist trivialerweise stets erfüllt,
denn true ist die schwächste aller Nachbedingung.
{P} c {false}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-169
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Stärkste vs. Schwächste Zusicherung
Was bedeutet die Gültigkeit folgender Hoare-Tripel:
1
2
3
4
{true} c {Q} “Wenn c terminiert, dann gilt danach Q.”
Dies gilt für jeden beliebigen Anfangszustand!
{false} c {Q} Dieses Tripel ist trivialerweise stets erfüllt: Es
gibt ja keinen Zustand, der die Vorbedingung erfüllt, also muss
auch die Nachbedingung nicht gelten (sie kann aber gelten).
{P} c {true} Auch dieses Tripel ist trivialerweise stets erfüllt,
denn true ist die schwächste aller Nachbedingung.
{P} c {false} “Wenn P gilt, dann terminiert c nicht.”
Nachbedingung false gilt ja per Definition nie. Für jeden
Zustand, der P erfüllt, kann c also nicht terminieren, falls das
Tripel wirklich beweisbar ist.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-169
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Ausdrücke als Statement
BNF
hstatementi ::= . . .
| hexpressioni;
Bedeutung
Ist e ein beliebiger Ausdruck, so ist also e; ein Statement.
Der Ausdruck e wird ausgewertet und sein Ergebnis verworfen.
Das macht natürlich nur Sinn, wenn e Seiteneffekte hat.
Das Zuweisungsstatement x=e; ist formal ein Spezialfall des
Ausdrucksstatements, da x=e auch ein Ausdruck ist: Seine
Auswertung weist als Seiteneffekt den Wert von e der Variablen x
zu. Wert dieses Ausdrucks ist der Wert von e.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-170
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Zuweisungen als Ausdruck
Das Zuweisungen eigentlich Ausdrücke mit Seiteneffekt sind,
könnte man so ausnutzen:
int n = 0;
while (10 > (n=n+1)) {
System.out.print(" n="+n);
}
//Ausgabe: " n=1 n=2 n=3 n=4 n=5 n=6 n=7 n=8 n=9"
Ein Beispiel für Bedingung mit Seiteneffekt! Die behandelten
Hoare-Regeln können hier also nicht angewendet werden!
Nicht besonders leserlich. Besser eigene Zuweisungen:
int n = 0; n=n+1;
while (10 > n) { System.out.print(" n="+n); n=n+1; }
Andererseits ist schon sehr übersichtlich, alle Information über die
Schleife (Start, Bedingung, Schritt) am Anfang der Schleife
gesammelt zu haben ⇒ for-Schleife.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-171
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Zuweisungen als Ausdruck
Das Zuweisungen eigentlich Ausdrücke mit Seiteneffekt sind,
könnte man so ausnutzen:
int n = 0;
while (10 > (n=(n=n+1)+1)) {
System.out.print(" n="+n+);
}
//Ausgabe: " n=2 n=4 n=6 n=8"
Ein Beispiel für Bedingung mit Seiteneffekt! Die behandelten
Hoare-Regeln können hier also nicht angewendet werden!
Nicht besonders leserlich. Besser eigene Zuweisungen:
int n = 0; n=n+1;
while (10 > n) { System.out.print(" n="+n); n=n+1; }
Andererseits ist schon sehr übersichtlich, alle Information über die
Schleife (Start, Bedingung, Schritt) am Anfang der Schleife
gesammelt zu haben ⇒ for-Schleife.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-171
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
For-Schleife
Oft muss ein Statement eine bestimmte, feste Zahl von Malen
durchlaufen werden.
int summe = 0;
for(int i = 0; i <= 100; i = i + 1) {
summe = summe + i;
}
Nach Ausführung ist summe gleich 5050.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-172
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Formelle Berschreibung in BNF
hstatementi ::= . . .
| for(hexpressioni;hexpressioni;hexpressioni)
hstatementi
Ausführung von
for(init;cond;step)body:
init und step sind Ausdrücke mit Seiteneffekten (Zuweisungen
oder Methodenaufrufe).
cond ist ein Ausdruck vom Typ Boolean.
body ist ein beliebiges Statement.
Dies kann ähnlich zu folgender while-Schleife lesen:
init;
while(cond){
rumpf
step;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-173
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Formelle Berschreibung in BNF
hstatementi ::= . . .
| for(hexpressioni;hexpressioni;hexpressioni)
hstatementi
Ausführung von
for(init;cond;step)body:
init und step sind Ausdrücke mit Seiteneffekten (Zuweisungen
oder Methodenaufrufe).
cond ist ein Ausdruck vom Typ Boolean.
body ist ein beliebiges Statement.
Dies kann ähnlich zu folgender while-Schleife lesen:
Unterschied:
init;
Die Lebensspanne von
while(cond){
Variablen, welche im initrumpf
Block deklariert sind, enstep;
det mit der Schleife!
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-173
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Beispiel: Zeichenkette umdrehen
In der Klasse String gibt es die Methode length und charAt.
Man verwendet sie z.B. so:
"Matthias".length() ist 8
"Matthias".charAt(2) ist 't'
Die Methode charAt liefert ein Ergebnis vom Typ char. Man kann
chars mit + an strings anhängen.
Wir wollen jetzt einen beliebigen String s umdrehen, also aus
Matthias soll saihttaM werden.
Dazu müssen wir s der Reihe nach durchgehen und die einzelnen
Zeichen in umgekehrter Reihenfolge aneinanderhängen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-174
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Lösung
String t = "";
for (int i = 0 ; i < s.length() ; i++) {
t = s.charAt(i) + t;}
Wir können den String auch vom Ende her durchgehen:
String t = "";
for (int i = s.length()-1 ; i >=0 ; i--) {
t = t + s.charAt(i);}
Merke: Im Rumpf einer While oder For-Schleife ist die Bedingung
immer erfüllt.
Unmittelbar nach einer While oder For-Schleife ist die Bedingung
immer falsch.
Frage: Was ist eine geeignete Invariante für diese Schleife?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-175
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Do-While-Schleife
Eine weitere alternative Schleife ist die do-while-Schleife. Der
einzige Unterschied zur while-Schleife ist, dass der Schleifenrumpf
mindestens einmal durchlaufen wird.
Dies ist manchmal praktisch, um eine Sonderbehandlung am
Anfang zu vermeiden.
BNF
hstatementi ::= . . .
| do hstatementi while(hexpressioni);
Das Statement do rumpf ; while(cond ); kann ähnlich zu
folgender while-Schleife lesen:
rumpf;
while(cond){
rumpf;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-176
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Do-While-Schleife
Eine weitere alternative Schleife ist die do-while-Schleife. Der
einzige Unterschied zur while-Schleife ist, dass der Schleifenrumpf
mindestens einmal durchlaufen wird.
Dies ist manchmal praktisch, um eine Sonderbehandlung am
Anfang zu vermeiden.
BNF
hstatementi ::= . . .
| do hstatementi while(hexpressioni);
Das Statement do rumpf ; while(cond ); kann ähnlich zu
folgender while-Schleife lesen:
Unterschied:
rumpf;
while(cond){
rumpf;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Lebensspanne von Variablen,
welche im rumpf -Block deklariert sind, gilt sonst nur für
einen Schleifendurchlauf!
Iteration
4-176
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Geschachtelte Schleifen
Im Rumpf einer Schleife darf wieder eine Schleife stehen.
Anwendungsbeispiel:
Ausgabe einer Tabelle der Potenzen x y für x = 1..10, y = 1..8.
1
2
3
4
5
6
7
8
9
10
1
4
9
16
25
36
49
64
81
100
Martin Hofmann, Steffen Jost
1
8
27
64
125
216
343
512
729
1000
1
16
81
256
625
1296
2401
4096
6561
10000
1
32
243
1024
3125
7776
16807
32768
59049
100000
Einführung in die Programmierung
1
64
729
4096
15625
46656
117649
262144
531441
1000000
1
1
128
256
2187
6561
16384
65536
78125
390625
279936
1679616
823543
5764801
2097152 16777216
4782969 43046721
10000000 100000000
Iteration
4-177
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Lösung
public class Powers{
public static void main(String[] args){
final int COLUMN_WIDTH = 10;
for (int x = 1; x <= 10; x++) {
for (int y = 1; y <= 8; y++) {
int p = (int)Math.round(Math.pow(x,y));
String pstr = "" + p;
/* Auffuellen bis zur COLUMN_WIDTH */
while(pstr.length() < COLUMN_WIDTH)
pstr = " " + pstr;
System.out.print(pstr);
}
}
}
}
System.out.println();
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-178
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Computersimulation
Auf einem Papier befinden sich parallele Linien im Abstand 2cm.
Eine Nadel der Länge 1cm wird zufällig auf das Papier geworfen.
Wie wahrscheinlich ist es, dass eine Linie getroffen wird?
Das untere Ende der Nadel liege auf Höhe 0 ≤ ylow ≤ 2 (bezogen
auf die Linie unmittelbar unterhalb der Nadel)
Der Winkel der Nadel betrage 0 ≤ α ≤ 180.
Beide Größen (ylow und α) seien gleichverteilt.
Das obere Ende der Nadel liegt dann auf Höhe
yhigh = ylow + sin(α).
Ein Treffer liegt vor, wenn yhigh ≥ 2.
Idee Bestimmung der Trefferrate durch Simulation!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-179
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Zufallsgenerator
Die Klasse Random stellt einen Zufallsgenerator bereit. Die Methode
nextDouble() liefert “zufälligen” Double-Wert im Bereich [0, 1]
import java.util.Random;
public class RandomTest{
public static void main(String[] args){
Random gen = new Random();
System.out.println(""+ gen.nextDouble() + " "
+ gen.nextDouble());
}
}
Druckt zwei “Zufallszahlen” aus, z.B.:
0.3119991282517587 0.2614453715060384
Der Aufruf gen.nextInt(n) liefert einen “zufälligen” Integer im
Bereich 0 . . . n − 1.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-180
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Buffon - Simulation
import java.util.Random;
public class Buffon
{ public static void main(String[] args){
Random generator = new Random();
int hits = 0;
final int NTRIES = 100000000;
for (int i = 1; i <= NTRIES; i++) {
double ylow = 2 * generator.nextDouble();
double angle = 180. * generator.nextDouble();
double yhigh= ylow + Math.sin(Math.toRadians(angle))
if (yhigh >= 2) hits++;
}
System.out.println("Tries / hits: " + (NTRIES * 1.0) / hits)
}}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-181
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Ergebnis
. . . dauert ein paar Minuten und ist
Tries / hits: 3.1414311574995777
Die Wahrscheinlichkeit beträgt also ungefähr
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
1
π.
Iteration
4-182
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Analytische Lösung
=
=
=
=
Pr(ylow + sin(α) ≥ 2)
Z
1 2
Pr(sin(α) ≥ 2 − y ) dy
2 y =1
Z
1 2
1 − Pr(sin(α) ≤ 2 − y ) dy
2 y =1
Z
1 2
1 − 2 · Pr(α ≤ arcsin(2 − y )) dy
2 y =1
Z
1 2
1 − 2 · arcsin(2 − y )/π dy
2 y =1
Berechnung des Integrals liefert
Martin Hofmann, Steffen Jost
1
π
≡ 0.3183098861837907
Einführung in die Programmierung
Iteration
4-183
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Weitere Beispiele
Was wird hier gedruckt?
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++)
System.out.print(i * j % 10);
System.out.println();}
Wie oft werden diese Schleifen ausgeführt?
for
for
for
for
(i = 1; i <= 10; i++) ...
(i = -10; i <= 10; i++) ...
(i= -10; i <= 10; i = i + 3) ...
(i= -10; i <= 10; i = i - 1) ...
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-184
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Übungsaufgabe: Random Walk
vgl. [Horstmann]
Man simuliere die Wanderung eines “Betrunkenen” in einem
“Straßengitter”.
Man zeichne ein Gitter von 10 × 10 Straßen (=Linien) und
repräsentiere den “Betrunkenen” als Kreuz in der Mitte des Gitters.
Eine bestimmte Zahl von Malen, z.B. 100, lasse man den
Betrunkenen zufällig eine Richtung (N, O, S, W), bewege ihn einen
Block weiter in dieser Richtung und zeichne ihn neu.
Anschließend bestimme man die zurückgelegte Entfernung.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-185
Schleifen
while-Schleife Invariante Hoare Logik for-Schleife do-Schleife Beispiele
Zusammenfassung: Schleifen
Schleifen dienen zur wiederholten Ausführung von Statements.
Es gibt die while-Schleife und die for-Schleife.
Die for-Schleife wird benutzt, wenn im Verlauf der Schleife
ein numerischer Wert in konstanten Schritten herauf- oder
heruntergezählt wird.
Invarianten dienen dazu, sich von der Korrektheit einer Schleife
zu überzeugen.
Bevor man eine Schleife programmiert, sollte man an die
mögliche Invariante denken!
Hoare Logik formalisiert die Methode, die Korrektheit eines
Programmes zu beweisen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-186
Ein- und ausgabe mit Konsole
Dialogfenster
Iteration zur Abarbeitung von Eingaben
import java.util.Scanner;
public class Woerter {
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
while (console.hasNextInt()) {
System.out.println(console.nextInt());
}
System.out.print(console.next()+"ist keine Zahl!");
}
}
Scanner.next() liefert nächste Eingabe als String
Scanner.nextInt() liefert int; löst Fehler aus, falls die
Eingabe keine ganze Zahl war!
Scanner.hasNextInt() prüft, ob nächste Eingabe eine ganze
Zahl ist.
Jede dieser Befehl kann Eingabeaufforderung auslösen, falls keine
Eingabe vorhanden! Scanner.hasNextInt() beläßt Eingabe im
Scanner – nächster Befehl löst dann keine Eingabeaufforderung aus.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-187
Ein- und ausgabe mit Konsole
Dialogfenster
Eingabe über die Konsole
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
while (console.hasNext()) {
System.out.println(console.next());
}
console.close();
}
Die Methode next() liefert das jeweils nächste Token
Ein Token ist ein durch Leerzeichen voneinander getrennte
Teile der Eingabe Man kann auch andere Symbole als
Trennzeichen festlegen.
Methode hasNext() prüft, ob noch Tokens vorhanden sind.
Methode nextLine() liefert komplette Eingabezeile.
Aufruf close() schliesst Eingabe. Nicht vergessen, vor allem
bei Datei-Operationen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-188
Ein- und ausgabe mit Konsole
Dialogfenster
Umlenkung von Ein-/Ausgabe
Man kann die Ausgabe eines Programms in eine Datei umlenken
und die Eingabe von einer Datei nehmen:
java Woerter < eingabe.in > ausgabe.out
liest statt von der Tastatur aus der Datei eingabe.in und schreibt
statt auf den Bildschirm auf ausgabe.out.
java Woerter < eingabe.in
geht auch und ebenso
java Woerter > ausgabe.out
ja sogar
java Woerter < eingabe.in | sort | java Unique > ausgabe.out
wenn etwa java Unique aufeinanderfolgende Dubletten entfernt.
Mit der Unix-pipe | lenkt man die Ausgabe eines Programms direkt
in ein anderes Programm als Eingabe um.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-189
Ein- und ausgabe mit Konsole
Dialogfenster
Eingabe per Dialogfenster
Alternativ können Benutzereingaben auch hübscher über ein
Dialogfenster getätigt werden:
import javax.swing.JOptionPane;
public class Main {
public static void main(String[] args) {
String input = JOptionPane.showInputDialog("Eingabe: ");
System.out.println("Input war: "+input);
}
}
Aufruf JOptionPane.showInputDialog(message) öffnet ein
Dialogfenster und liefert den eingegebenen Text als String zurück.
Vorher mit import javax.swing.JOptionPane; importieren.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-190
Ein- und ausgabe mit Konsole
Dialogfenster
Zahlen per Dialogfenster
import javax.swing.JOptionPane;
public class Main {
public static void main(String[] args) {
String msg
= "Bitte Zahl eingeben: ";
String input = JOptionPane.showInputDialog(msg);
int x = Integer.parseInt(input);
System.out.println("Zahl war: "+x);
}
}
Um Zahlen einzulesen, muss man diese mit Methoden wie etwa
Integer.parseInt umrechnen.
Hier muss man aber immer eine Fehlerbehandlung durchführen,
wenn keine Zahl eingegeben wurde.
Fehlerbehandlung kommt aber erst später
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Iteration
4-191
Einführung in die Programmierung
mit Java
Teil 5: Syntax
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
10. November 2015
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-192
Inhalt Teil 5: Syntax
15
Syntax und Semantik
Formale Sprachen
Backus-Naur Form
Chomsky Grammatik
Reguläre Ausdrücke
16
Endliche Automaten
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-193
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Syntax
Syntax: Festlegung des Satzbaus.
Beispiele syntaktisch falscher deutscher Sätze:
Kai liest eine Buch.
Buch lesen Kai.
Kai pr1&.
Kai liest ein Buch, wenn ihr ist langweilig.
Beispiele syntaktisch falscher Java-Phrasen:
{{x=1;};
if x==1 y=2;
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-194
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Semantik
Semantik: Festlegung der Bedeutung eines Satzes.
Beispiele Semantische Fragen im Deutschen:
Worauf bezieht sich ein Relativpronomen?
Welchen Einfluss haben Fragepartikel wie “eigentlich”, “denn”?
Wann verwendet man welche Zeitform?
Beispiele Semantische Fragen bei Java:
In welcher Reihenfolge werden Ausdrücke ausgewertet?
Wie werden Instanzvariablen initialisiert?
Was ist ein Objekt?
Grundfrage der Semantik von Programmiersprachen:
Welche Wirkung hat ein syntaktisch korrektes Programm?
Fragen der Typüberprüfung werden aus historischen Gründen
ebenfalls der Semantik zugerechnet.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-195
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Formale Syntax
Ein Alphabet, oft bezeichnet mit Σ, ist eine endliche Menge,
deren Elemente Symbole genannt werden.
Eine Zeichenkette (auch Wort) über Alphabet Σ ist eine
endliche Folge von Elementen σ1 , . . . , σn aus Σ, mit n ≥ 0.
Man schreibt ein Wort als σ1 σ2 . . . σn .
Der Fall n = 0 bezeichnet das leere Wort geschrieben ε.
Die Menge aller Wörter über Σ wird mit Σ∗ bezeichnet. Zu
0 bildet man die
zwei Wörtern w = σ1 . . . σn und w 0 = σ10 . . . σm
0
0 .
Verkettung (Konkatenation) ww = σ1 . . . σn σ10 . . . σm
0
00
0
00
Es gilt εw = w ε = w und (ww )w = w (w w ).
Eine formale Sprache ist eine Teilmenge von Σ∗ .
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-196
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Beispiele
1
Σ = {a, b}.
Wörter über Σ: aaba, baab, bababa, baba, ε, bbbb.
Sprachen über Σ: { }, {a, b}, {an bn | n ∈ N}.
Beachte: an bedeutet hier: aa
. . . a}.
| {z
n-Stück
2
3
Σ = {0, 1, . . . , 9, e, -, +, ., E}.
Wörter über Σ: -1E98, --2e--, 32.e
Sprachen über Σ: Syntaktisch korrekte double Konstanten,
{en | n gerade}.
Σ = {0, . . . , 9, if, while, ;, {, }, . . . }
Sprache über Σ: alle syntaktisch korrekten Java Programme.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-197
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Beispiel: Bezeichner
Bezeichner sind Zeichenketten über dem Alphabet
Σ = {A, . . . , Z, a, . . . , z, 0, . . . , 9, _}. Bezeichner müssen mit
einem Buchstaben oder dem Zeichen _ beginnen.
Ein Buchstabe ist ein Kleinbuchstabe oder ein
Großbuchstabe
Ein Kleinbuchstabe ist ein Zeichen a, . . . , z.
Ein Großbuchstabe ist ein Zeichen A, . . . , Z.
Eine Ziffer ist ein Zeichen 0, . . . , 9.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-198
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Backus-Naur Form
Backus Naur Form (BNF) für formelle Beschreibung von Sprachen:
hnamei
··· | ···
Courier-Text
[. . . ]
(. . . )+
(. . . )∗
bezeichnet Menge von Sprachelementen
trennt mehrere Alternativen voneinander
bezeichnet wörtliche Anteile
bedeutet “optional”
bedeutet “mindestens eins oder mehrere”
bedeutet “keins oder mehrere”
Beispiel: Java-Statements
hstatementi ::= htype_expressioni hidenti [= hexpressioni];
| { (hstatementi)+ }
| if ( hexpressioni) hstatementi [else hstatementi]
| ...
Bemerkung: Strenggenommen verwenden wir hier die Erweiterte
BNF (EBNF). Siehe z.B. Wikipedia.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-199
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
BNF für gültige Bezeichner
Weiteres Beispiel: BNF für gültige Bezeichner
hBezeichneri ::= (hBuchsti | _)(hBuchsti | hZifferi | _)∗
hBuchsti
::= hKlBuchsti | hGrBuchsti
hKlBuchsti
::= a | b | c | d | e | f | g | h | i | j | k | l | m | n | o
|p|q|r|s|t|u|v|w|x|y|z
hGrBuchsti
::= A | B | C | D | E | F | G | H | I | J | K | L | M | N | O
|P|Q|R|S|T|U|V|W|X|Y|Z
hZifferi
::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-200
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
double-Literale
Literal = Konstante in einer Programmiersprache.
hdouble-Literali
hVorzeicheni
hMantissei
hExponenti
::=
::=
::=
::=
[hVorzeicheni]hMantissei[hExponenti]
-|+
(hZifferi)+ [.(hZifferi)∗ ] | .(hZifferi)+
(e | E)[hVorzeicheni](hZifferi)+
Zusätzliche Kontextbedingung: Entweder ein Dezimalpunkt, oder
ein e oder ein E muss vorhanden sein.
Übung: man verbessere die BNF Darstellung so, dass diese
Kontextbedingung wegfallen kann.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-201
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Syntax der BNF
Eine BNF-Grammatik ist ein Quadrupel G = (Σ, V , S, P).
Σ ist die Menge der Terminalsymbole, meist in Courier
gesetzt.
V ist die Menge der Nichtterminalsymbole, meist in spitze
Klammern gesetzt. Im Beispiel:
V = {hdouble-Literali, hMantissei, hVorzeicheni, hExponenti}.
S ∈ V ist ein ausgezeichnetes Nichtterminalsymbol, das
Startsymbol. Im Beispiel: S = hdouble-Literali.
P ist eine endliche Menge von Produktionen der Form
X ::= δ, wobei δ eine BNF-Satzform, s.u., ist.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-202
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
BNF-Satzformen
Jedes Symbol in V ∪ Σ ist eine BNF Satzform.
Sind γ1 , . . . , γn BNF-Satzformen, so auch γ1 | · · · | γn
(Auswahl).
Sind γ1 , . . . , γn BNF-Satzformen, so auch γ1 . . . γn
(Verkettung).
Ist γ eine BNF Satzform, so auch (γ) (Klammerung).
Ist γ eine BNF Satzform, so auch (γ)∗ (Iteration).
Ist γ eine BNF Satzform, so auch (γ)+ (nichtleere Iteration).
Ist γ eine BNF Satzform, so auch [γ] (Option).
Eine Satzform der Gestalt γ1 | · · · | γn muss immer geklammert
werden, es sei denn, sie tritt unmittelbar als rechte Seite einer
Produktion auf.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-203
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Semantik der BNF
Sei G = (Σ, V , S, P) und X ∈ V ein Nichtterminalsymbol. Ein
Wort w ist aus X herleitbar (“ist ein X ”, “ist in L(X )”), wenn man
es aus X durch die folgenden Ersetzungsoperationen erhalten kann:
Falls X ::= γ1 | · · · | γn eine Produktion ist, so darf man ein
Vorkommen von X durch eines der γi ersetzen.
Ein Vorkommen von (γ1 | · · · | γn ) darf man durch eines der γi
ersetzen.
n-mal
z
}|
{
Ein Vorkommen von (γ)∗ darf man durch (γ)(γ) . . . (γ) mit
n ≥ 0 ersetzen.
n-mal
z
}|
{
+
Ein Vorkommen von (γ) darf man durch (γ)(γ) . . . (γ) mit
n > 0 ersetzen.
Ein Vorkommen von (γ) darf man durch γ ersetzen, wenn γ
nicht von der Form γ1 | · · · | γn ist.
Ein Vorkommen von [γ] darf man durch (γ) ersetzen, oder
ersatzlos streichen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-204
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Beispiel
hdouble-Literali → [hVorzeicheni]hMantissei[hExponenti]
→ hMantissei[hExponenti]
→ (hZifferi)+ [.(hZifferi)∗ ][hExponenti]
→ (hZifferi)+ .(hZifferi)∗ [hExponenti]
→ (hZifferi)+ .(hZifferi)(hZifferi)[hExponenti]
→ (hZifferi).(hZifferi)(hZifferi)[hExponenti]
→ 2.71hExponenti
→ 2.71(e | E)[hVorzeicheni](hZifferi)+
→ 2.71EhVorzeicheni(hZifferi)(hZifferi)(hZifferi)
→ 2.71E(- | +)001 → 2.71E-001
Also 2.71E-001 ∈ L(hdouble-Literali).
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-205
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Kontextbedingungen
Manche syntaktische Bedingungen lassen sich mit BNF nur schwer
oder gar nicht formulieren.
Man gibt daher manchmal zusätzliche Kontextbedingungen an,
denen die syntaktisch korrekten Wörter zusätzlich genügen müssen.
Beispiele:
Bezeichner dürfen nicht zu den Schlüsselwörtern gehören wie
z.B. let, if, etc.
double-Literale müssen ., e, oder E enthalten.
Andere Bedingungen, wie korrekte Typisierung oder rechtzeitige
Definition von Bezeichnern werden, wie schon gesagt, der Semantik
zugerechnet.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-206
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Varianten
Oft (und in der Java-Sprachdefinition) wird statt (γ)∗ auch
{γ} geschrieben und (γ)+ wird nicht verwendet.
Die spitzen Klammern zur Kennzeichnung der
Nichtterminalsymbole werden oft weggelassen.
Steht kein Courier Zeichensatz zur Verfügung, so schließt
man die Terminalsymbole in “Anführungszeichen” ein.
Statt des Produktionssymbols ::= wird manchmal, speziell
auch in der Java-Sprachdefinition, ein einfacher Doppelpunkt
verwendet.
Die Java Sprachdefinition setzt Alternativen durch separate
Zeilen ab anstatt durch |.
BNF Grammatiken lassen sich auch grafisch in Form von
Syntaxdiagrammen darstellen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-207
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Ableitungsbäume
Ableitungen in einer BNF lassen sich grafisch durch
Ableitungsbäume darstellen.
Diese Ableitungsbäume sind für die Festlegung der Semantik von
Bedeutung.
Ein Parser berechnet zu einem vorgegebenen Wort einen
Ableitungsbaum, falls das Wort in L(S) ist, und erzeugt eine
Fehlermeldung, falls nicht.
Diese Aufgabe bezeichnet man als Syntaxanalyse.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-208
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Struktur eines Compilers
Vereinfacht!
Lexikalische Analyse:
Einlesen der Dateien
Zerlegung in Tokens
(Bezeichner, Literale, Schlüsselworte, Operatorsymbole,. . . )
Syntaxanalyse: Erstellen eines Syntaxbaumes
Semantische Analyse (Typüberprüfung, Platzzuweisung)
Optimierung
Codeerzeugung
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-209
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Java-Sprachdefinition (Fragment)
Statement:
StatementWithoutTrailingSubstatement
IfThenStatement
IfThenElseStatement
WhileStatement
ForStatement
StatementWithoutTrailingSubstatement:
Block
EmptyStatement
ExpressionStatement
ReturnStatement
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-210
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
StatementNoShortIf:
StatementWithoutTrailingSubstatement
IfThenElseStatementNoShortIf
WhileStatementNoShortIf
ForStatementNoShortIf
IfThenStatement:
if ( Expression ) Statement
IfThenElseStatement:
if ( Expression ) StatementNoShortIf else Statement
IfThenElseStatementNoShortIf:
if ( Expression ) StatementNoShortIf else
StatementNoShortIf
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-211
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
WhileStatement:
while ( Expression ) Statement
WhileStatementNoShortIf:
while ( Expression ) StatementNoShortIf
Block:
{ [BlockStatements] }
BlockStatements:
BlockStatement
BlockStatements BlockStatement
BlockStatement:
LocalVariableDeclarationStatement
ClassDeclaration
Statement
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-212
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Parsergeneratoren
Erinnerung: Parser : Σ∗ → Ableitungsbäume ∪ Fehlermeldungen.
Ein Parsergenerator erzeugt aus einer BNF-Grammatik
automatisch einen Parser.
Der bekannteste Parsergenerator heißt “yacc” (yet another
compiler-compiler). Er erzeugt aus einer BNF-Grammatik einen in
der Programmiersprache C geschriebenen Parser.
Für Java gibt es “JavaCUP”, “AntLR”, u.a. Es wird ein in Java
geschriebener Parser erzeugt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-213
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Geschichte
Grammatikformalismen, die syntaktisch korrekte Sätze durch einen
Erzeugungsprozess definieren (wie die BNF) heißen generative
Grammatiken.
Sie gehen auf den Sprachforscher Noam Chomsky (1928– ) zurück.
Eine kontextfreie Chomsky-Grammatik ist eine BNF-Grammatik
ohne die Konstrukte [ ], |, ( )+ , ( )∗ . Man kann jede BNF-Grammatik
durch eine kontextfreie Chomsky-Grammatik simulieren.
Chomsky betrachtete u.a. auch kontextsensitive Grammatiken.
Die Backus-Naur-Form wurde von den Informatikern John Backus
und Peter Naur entwickelt, die die Bedeutung von Chomskys
generativen Grammatiken für Programmiersprachensyntax
erkannten.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-214
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Beispiel einer Chomsky Grammatik
hBezeichneri
hBezeichneri
hFolgei
hFolgei
hZeicheni
hZeicheni
hZeicheni
hZeicheni
hBuchsti
hBuchsti
Martin Hofmann, Steffen Jost
::=
::=
::=
::=
::=
::=
::=
::=
::=
::=
hKlBuchstihFolgei
_ hFolgei
hZeichenihFolgei
ε
’
_
hBuchsti
hZifferi
hKlBuchsti
hGrBuchsti
Einführung in die Programmierung
Syntax
5-215
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Reguläre Ausdrücke
Eine rechte Seite einer BNF ohne Nichtterminalsymbole ist ein
regulärer Ausdruck.
Formal werden reguläre Ausdrücke (über einem Alphabet Σ) wie
folgt gebildet:
Jedes Terminalsymbol a ∈ Σ ist ein regulärer Ausdruck.
ist ein regulärer Ausdruck.
Sind E1 , E2 , . . . , En reguläre Ausdrücke, so auch E1 E2 . . . En
und E1 | E2 | · · · | En .
Ist E regulärer Ausdruck, so auch E ∗ und E + und [E ]
Zu jedem regulären Ausdruck E bezeichnet man mit L(E ) die durch
ihn definierte Sprache über Σ.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-216
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Beispiele mit Σ = {a, b}
Wenn E = (a|b)∗ bbb so ist
L(E ) = {w ∈ Σ∗ | w hört mit bbb auf}.
Wenn E = (ab | aabb | aab(ab)∗ b)∗ so besteht L(E ) aus allen
Wörtern w , sodass gilt:
w enthält genausoviele a’s wie b’s.
Ist u ein Anfangsstück von w , so enthält u mindestens soviele
a’s wie b’s und die Zahl der a’s übersteigt die der b’s um
höchstens zwei.
Dieselbe Sprache wird auch durch (a(ab)∗ b)∗ definiert.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-217
Syntax und Semantik
Formale Sprachen BNF Chomsky Grammatik Reguläre Ausdrücke
Bemerkung zur Tradition
Traditionell erlaubt man Konkatenation und Alternative nur für
n = 2 und fügt ε (leeres Wort) { } (leere Sprache) explizit hinzu.
Außerdem sind E + und [E ] “ublicherweise nicht Teil der offiziellen
Syntax für reguläre Ausdrücke und werden durch EE ∗ und E | wiedergegeben.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-218
Endliche Automaten
Deterministische endliche Automaten
Ein deterministischer endlicher Automat (DEA) über einem
Alphabet Σ besteht aus
Einer endlichen Menge Q von Zuständen
Für jeden Zustand q ∈ Q und Zeichen σ ∈ Σ ein Folgezustand
δ(q, σ) ∈ Q. Die Zuordnung (q, σ) 7→ δ(q, σ) ist die
Zustandsüberführungsfunktion.
Einem Anfangszustand q0 ∈ Q
Einer Teilmenge F ⊆ Q von akzeptierenden Zuständen.
Formal ist ein DEA also ein Tupel A = (Σ, Q, δ, q0 , F ).
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-219
Endliche Automaten
Beispiel
Grafische Darstellung eines Automaten:
a
A
a
B
b
a
C
D
a,b
b
b
Formal: Q = {A, B, C , D}, q0 = A und F = {A},
Lauf von A auf aababaabab:
δ
A
B
C
D
a
B
C
D
D
b
D
A
B
D
Martin Hofmann, Steffen Jost
a a
A B C
b a
B C
b a
B C
a b a b
D D D D
. . . und auf ababaababb:
a b a b a a
A B A B A B C
Einführung in die Programmierung
b a
B C
Syntax
b b
B A
5-220
Endliche Automaten
Akzeptierte Sprache
Gegeben ein endlicher Automat A = (Σ, Q, q0 , F ) und ein Wort
w = σ0 σ1 · · · σn−1 .
Der Lauf von A auf w ist die durch Abarbeiten von w gemäß δ von
q0 aus entstehende Zustandsfolge:
q0 , q1 , q2 , . . . , qn
wobei q0 der Anfangszustand ist und qi+1 = δ(qi , σi ).
Der Automat akzeptiert das Wort w , wenn qn ∈ F , wenn also
nach Abarbeitung des Wortes w ein Zustand aus F erreicht wird.
Die Sprache L(A) des Automaten A umfasst alle Wörter, die A
akzeptiert.
Im Beispiel ist die Sprache gerade die Sprache L (a(ab)∗ b)∗
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-221
Endliche Automaten
Automat in Java
import java.util.Scanner;
public class AutomatTest {
public static void main(String[] args) {
Scanner console
= new Scanner(System.in);
MeinAutomat automat = new MeinAutomat();
String eingabe
= console.nextLine();
boolean fehler
= false;
for (int i = 0; !fehler && i<eingabe.length(); i++) {
if (eingabe.charAt(i) == 'a')
automat.lies_a();
else if (eingabe.charAt(i) == 'b')
automat.lies_b();
else fehler = true;
}
if (fehler)
System.out.println("Falsche Eingabe.");
else if (automat.hatAkzeptiert())
System.out.println("Akzeptiert.");
else
System.out.println("Verworfen.");
}}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-222
Endliche Automaten
Der Automat selbst
public class MeinAutomat {
private int zustand;
private
private
private
private
final
final
final
final
int
int
int
int
A
B
C
D
=
=
=
=
0;
1;
2;
3;
public MeinAutomat() {
zustand = A; }
public void lies_a() {
if (zustand == A) zustand = B;
else if (zustand == B) zustand = C;
else if (zustand == C) zustand = D;
else if (zustand == D) zustand = D; }
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-223
Endliche Automaten
public void lies_b() {
if (zustand == A) zustand = D;
else if (zustand == B) zustand = A;
else if (zustand == C) zustand = B;
else if (zustand == D) zustand = D; }
public boolean hatAkzeptiert() {
return zustand == A;
}
}
public void zuruecksetzen() {
zustand = A; }
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-224
Endliche Automaten
Nichtdeterministische endliche Automaten
Ein nichtdeterministischer endlicher Automat (NEA) ist ein
Tupel
A = (Σ, Q, q0 , δ, F )
wobei δ einem Zustand q und Symbol σ eine Menge von
Zuständen δ(q, σ) zuordnet.
Ein Lauf eines NEA auf einem Wort w = σ0 σ1 . . . σn−1 ist dann
eine Zustandsfolge q0 , q1 , . . . , qn sodass qi+1 ∈ δ(qi , σi ).
Zu ein und demselben Wort gibt es i.a. mehrere Läufe.
Der NEA akzeptiert ein Wort, wenn ein Lauf existiert, der in einem
akzeptierenden Zustand endet.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-225
Endliche Automaten
Beispiel
b
A
b
B
b
C
D
a,b
Formal: Q = {A, B, C , D}, q0 = A und F = {D},
Akzeptierender Lauf von A auf aabbabbb:
δ
a
b
A {A} {A,B}
a a b b a b b b
B {}
{C}
A A A A A A B C D
C {}
{D}
D {}
{}
Sprache des Automaten: L(A) = L((a|b)∗ bbb)
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-226
Endliche Automaten
Äquivalenz von DEA, NEA, Reg.Ausdr.
Satz (ohne Beweis):
DEAs, NEAs und reguläre Ausdrücke haben die gleiche Stärke:
Zu jedem regulären Ausdruck E existiert ein NEA A mit
L(A) = L(E ).
Zu jedem NEA A existiert ein DEA A0 mit L(A) = L(A0 )
(allerdings hat A0 im allgemeinen sehr viel mehr Zustände als
A!).
Zu jedem DEA A existiert ein regulärer Ausdruck E mit
L(E ) = L(A).
DEA für (a|b)∗ bbb.
b
A
b
AB
b
ABC
a
a
a
Martin Hofmann, Steffen Jost
AB
CD
b
Die Zustände des DEA
entsprechen Mengen von
Zuständen des vorher gezeigten NEA.
Einführung in die Programmierung
Syntax
5-227
Endliche Automaten
Reguläre Sprachen
Eine Sprache L ⊆ Σ∗ ist regulär, wenn ein DEA A (alternativ NEA
oder regulärer Ausdruck) existiert mit L = L(A).
Nicht jede durch eine BNF beschreibbare Sprache ist regulär.
Beispiel: Korrekt geklammerte arithmetische Ausdrücke.
Sprachen, die von BNF oder kontextfreien Grammatiken
beschrieben werden, heißen kontextfrei.
Nicht jede Sprache ist kontextfrei: Beispiel {ww | w ∈ Σ∗ }
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-228
Endliche Automaten
Zusammenfassung Syntax
Formale Sprachen sind einfach Mengen von Zeichenketten.
BNF ist ein System zur Definition formaler Sprachen.
Ein Parser berechnet zu gegebener BNF B und Wort w
entweder einen Ableitungsbaum oder liefert die Information,
dass w nicht zur von B definierten Sprache gehört.
Reguläre Ausdrücke sind ein Spezialfall der BNF und erlauben
die Definition regulärer Sprachen.
Deterministische endliche Automaten beschreiben auch
reguläre Sprachen und lassen sich unmittelbar implementieren.
Nichtdeterministische endliche Automaten erlauben kürzere
Beschreibungen und lassen sich durch Einführung von
Zustandsmengen als DEA implementieren.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Syntax
5-229
Einführung in die Programmierung
mit Java
Teil 6: Arrays
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
17. November 2015
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-230
Inhalt Teil 6: Arrays
17
Arrays in Java
Syntax, Typisierung, Semantik
Arrays und Methoden
Grundlegende Algorithmen mit Arrays
Arrays und Objekte
Mehrdimensionale Arrrays
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-231
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Arrays: Motivation
Wir wollen 10 Preise einlesen und den niedrigsten markieren:
19.95
23.95
24.95
18.95 <-- niedrigster Preis
29.95
19.95
20.00
22.99
24.95
19.95
Alle Daten müssen eingelesen werden, bevor wir ausgeben können,
daher müssen wir sie zwischenspeichern.
Dafür zehn Variablen zu verwenden wäre sehr unflexibel.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-232
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Arrays
Durch
double[] data = new double[10];
deklarieren wir ein Array von double-Werten der Größe 10.
Genauer:
Ein Array ist ein Verweis auf eine Abfolge fester Länge von
Variablen des gleichen Typs, genannt Fächer (engl. slots).
Der Typ typ[] ist der Typ der Arrays mit Einträgen vom Typ
typ.
Der Ausdruck new typ[n] liefert ein frisches Array der Länge n
zurück, mit Einträgen des Typs typ. Hier ist n ein Ausdruck
vom Typ int.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-233
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Durch
double[] data = new double[10];
In diesem Beispiel ist also data eine Arrayvariable, die ein frisches
Array vom Typ double der Länge 10 enthält.
Im Rechner ist der Arrayinhalt als Block aufeinanderfolgender
Speicherzellen repräsentiert. Das Array selbst ist ein Objekt
bestehend aus der Länge und der Speicheradresse des Blocks.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-234
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Arrayzugriff
Durch
data[4] = 29.95;
setzen wir das Fach mit der Nummer 4 auf den Wert 29.95. Mit
System.out.println("Der Preis ist EUR" + data[4]);
können wir diesen Wert ausgeben.
data[4] verhält sich ganz genauso wie eine “normale” Variable
vom Typ double.
Ist e ein Array und i ein Ausdruck des Typs int, so bezeichnet
e[i] das Fach mit Index i des Arrays e.
Achtung: Diese Indizes beginnen bei Null. Es muss also i echt
kleiner als die Länge von e sein. Im Beispiel sind die Fächer also
data[0], data[1], data[2], data[3], ... , data[9]
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-235
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Länge
Ist e ein Array, so ist e.length die Länge des Arrays als Integer.
Das ist nützlich, wenn man die Länge später (durch Editieren und
Neucompilieren) vergrößern muss. data.length stellt sich dann
automatisch mit um. Ein “festverdrahteter” Wert wie 10 müsste
auch explizit umgeändert werden.
Die Länge wird abgerufen wie eine als public deklarierte
Instanzvariable. Man kann sie aber im Programm nicht verändern,
d.h. data.length = 20 ist nicht erlaubt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-236
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Wichtiges
Ein Array ist ein Verweis auf eine Folge von Variablen, der
sog. Fächer.
Arraytypen werden durch Anfügen von eckigen Klammern
gebildet. Ein Arraytyp ist weder Objekt- noch Grunddatentyp.
Frische Arrays werden mit new erzeugt; die Länge des Arrays
wird in eckigen Klammern angegeben.
Die Fächer eines Arrays werden durch Anfügen des in eckige
Klammern gesetzten Index bezeichnet.
Ein auf diese Weise bezeichnetes Fach ist eine Variable. Man
kann ihren Wert verwenden und ihr mit = einen neuen Wert
zuweisen.
Arrayindizes beginnen immer bei 0.
Die Länge eines Arrays erhält man mit .length
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-237
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Einlesen der Daten
Wir wollen in unser Array data zehn Preise von der Konsole
einlesen. So geht es:
Scanner konsole = new Scanner(System.in);
for (int i = 0; i < data.length; i++)
data[i] = Double.parseDouble(konsole.nextLine());
Vorsicht: Man sollte nicht i <= data.length schreiben, das
würde zu einem Zugriffsfehler führen, da es das Fach mit Index
data.length nicht gibt.
In Java führen Zugriffsfehler zum Programmabbruch.
In C können sie dazu führen, dass beliebige Befehle unbeabsichtigt
ausgeführt werden (der gefürchtete buffer overflow).
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-238
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Idiom für die Arrayverarbeitung
Merke: Das “Durcharbeiten” eines Arrays erfolgt meist so:
for (int i = 0; i < a.length; i++) {
Bearbeiten des Faches mit Index i
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-239
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Besondere For-Schleife
Will man auf die Fächer nur lesend zugreifen, so kann man die
folgende Notation verwenden:
for (double x : a) {c }
ist äquivalent zu
for (int i = 0; i < a.length; i++) {
double x = a[i];
c
}
Zur Initialisierung von Arrays oder zu anderer schreibender
Bearbeitung ist diese Notation wertlos:
for (double x : a) {x = 3.141;}
lässt a unverändert und hat auch sonst keine sichtbare Wirkung.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-240
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Vorsicht mit Arrayvariablen
double[] data;
System.out.println(data[5]);
ist falsch. data[5] wurde ja nicht initialisiert.
double[] data;
data[5] = 7;
System.out.println(data[5]);
ist aber auch falsch!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-241
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
“new” nicht vergessen
Der Grund ist, dass die Variable data selber noch gar nicht
initialisiert wurde.
Man muss so einer Variable erst ein Array ( = Verweis auf Folge
von Variablen) zuweisen. Normalerweise macht man das mit new:
double[] data;
data = new double[10];
Man kann aber auch einen anderen Arrayausdruck zuweisen, z.B.
double[] kopie = data;
Dann aber Vorsicht mit Aliasing:
data[5] = 7; kopie[5] = 8; // data[5] ist jetzt 8
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-242
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Arrays kopieren
Um eine wirkliche Kopie von data zu erhalten, macht man
folgendes:
double[] kopie = new double[data.length];
for (int i = 0; i < data.length; i++)
kopie[i] = data[i];
oder kürzer mit der Methode System.arraycopy (siehe Doku).
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-243
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Arrays variabler Länge
Oft weiß man nicht von vornherein, wie groß ein Array sein muss.
Beispiel:
Benutzer gibt der Reihe nach Preise ein und hört mit 0 auf.
Man kann dann ein sehr großes Array bilden und es nur teilweise
füllen.
Eine zusätzliche int Variable gibt an, bis wohin man gefüllt hat.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-244
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Niedrigste Preise
public class Preise {
public static void main(String[] args) {
final int DATA_LENGTH = 1000;
Scanner konsole = new Scanner(System.in);
double[] data = new double[DATA_LENGTH];
int dataSize = 0;
boolean done = false;
System.out.println("Geben Sie die Preise ein,
beenden mit 0");
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
\
6-245
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
while (!done) {
double price = konsole.nextDouble();
if (price == 0) // Eingabeende
done = true;
else if (dataSize < data.length) {
data[dataSize] = price;
dataSize++;
} else { // Array voll
System.out.println("Das Array ist voll.");
done = true;
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-246
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
if (dataSize > 0) {
double lowest = data[0];
int lowestNo = 0;
for (int i = 1; i < dataSize; i++)
if (data[i] < lowest) {
lowest = data[i];
lowestNo = i;
}
for (int i = 0; i < dataSize; i++) {
System.out.print(data[i]);
if (i == lowestNo)
System.out.print(" <-- niedrigster Preis");
System.out.println(); }}}}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-247
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Arrays als Methodenparameter
Ein Array kann als Parameter übergeben werden:
public static double mittelwert(double[] zahlen) {
if (zahlen.length == 0) return 0.0;
double summe = 0;
for (int i = 0; i < zahlen.length; i++)
summe = summe + zahlen[i];
return summe / zahlen.length;
}
Man kann nun etwa mittelwert(data) aufrufen. Wert ist der
Mittelwert von data.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-248
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Arrays als Methodenparameter
Es wird nur das Array übergeben, d.h. der Verweis auf das Array.
Man kann daher (zuweilen unerwünschte) Seiteneffekte erhalten:
public static double f(double[] zahlen) {
if (zahlen.length == 0) return 23;
else {
zahlen[0] = 27;
return 23;
}
Der Aufruf f(data) hat stets den Wert 23, setzt aber gleichzeitig
data[0] auf 27.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-249
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Arrays als Rückgabewerte
Ein Arraytyp kann auch als Rückgabewert in Erscheinung treten.
Hier ist eine Methode, die die Eckpunkte eines regelmässigen
n-Ecks zurückgibt:
static Point[] nEck(int n, Point zentrum, double radius)
/* Gibt die Eckpunkte eines regelmaessigen Polygons
mit n Ecken, Zentrum zentrum und Radius radius aus
*/
{ Point[] result = new Point[n];
/* Formeln mit sin, cos geloescht.*/
return result;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-250
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Finden eines Wertes
Man möchte wissen, ob ein Preis ≤ 1000 ist:
boolean gefunden = false;
for (i = 0; i < data.length; i++)
gefunden = gefunden || (data[i] <= 1000.);
jetzt ist gefunden true genau dann, wenn data einen Eintrag
≤ 1000 hat.
Man möchte wissen, wieviele Preise ≤ 1000 sind (hier zur
Abwechslung mit der neuen Notation):
int count = 0;
for (double fach : data.length)
if (fach <= 1000.) count++;
Jetzt ist count gleich der Anzahl derjenigen ≤ 1000.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-251
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Löschen eines Wertes
Man möchte den Eintrag an der Stelle pos aus einem teilweise
gefüllten Array löschen.
Falls die Ordnung keine Rolle spielt:
data[pos] = data[dataSize-1];
dataSize = dataSize - 1;
Falls die Ordnung beibehalten werden muss:
for (int i = pos; i < dataSize - 1; i++)
data[i] = data[i+1];
dataSize = dataSize - 1;
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-252
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Einfügen eines Elements
. . . an der Stelle pos unter Beibehaltung der Ordnung:
for (int i = dataSize; i > pos; i--)
data[i] = data[i-1];
data[pos] = neuerWert;
dataSize = dataSize + 1;
Man muss sich immer wieder sehr genau klar machen, was hier
passiert.
In der Anwendung muss man natürlich sicherstellen, dass pos nicht
ausserhalb der Grenzen liegt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-253
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Arrays von Objekten
Hat man mehrere gleichlange Datensätze, so bietet es sich an, sie
als ein einziges Array, dessen Einträge Objekte sind, zu
repräsentieren:
Beispiel: Eine Liste von Automodellen, die Liste der zugehörigen
Preise, die Liste der zugehörigen PS-Zahlen.
public class Auto {
/* Instanzvariablen hier ausnahmsweise public */
public String modell;
public double preis;
public double psZahl;
/* Methoden und Konstruktoren */
}
Auto[] liste = ...
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-254
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Verändernder Zugriff mit der neuen
For-Schleife
Folgender Code erhöht alle Preise um 3%:
for(Auto auto : liste)
auto.preis = auto.preis * 1.03;
Hier wird auto der Reihe nach an die Werte der Fächer von liste
gebunden. Mithilfe dieser Werte, die ja Objektverweise sind, kann
man dann schreibend auf die Objekte selbst zugreifen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-255
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Arrays als Instanzvariablen
Arrays können auch als Instanzvariablen eines Objektes in
Erscheinung treten.
Es empfiehlt sich, dann im Konstruktor auch gleich ein frisches
Array zu erzeugen.
Beispiel:
public class Polygon {
private int n; // Zahl der Ecken
private Point[] ecken; // Liste der Ecken
/* Methoden und Konstruktoren */
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-256
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Vergrößern eines Arrays
Wird ein teilweise gefülltes Array zu klein, so kann man es in ein
größeres neu allokiertes Array umkopieren.
Es ist üblich, das neue Array von jeweils doppelter Größe zu wählen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-257
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Die Klasse ArrayList
Die vordefinierte Klasse java.util.ArrayList stellt Arrays
variabler Größe mit Methoden zum Einfügen, Löschen, etc. bereit.
Einschränkung: in einer ArrayList können nur Objekte stehen,
keine Werte von Grunddatentypen wie int, double, boolean.
Will man integers in einer ArrayList verwalten, so muss die
Wrapperklasse Integer verwendet werden. Seit Java 1.5 werden
ints automatisch in Integers konvertiert. Auto Boxing/Unboxing
Die Klasse ArrayList kann also mit dem Typ der Einträge
parametrisiert werden: ArrayList<Auto> ist die Klasse der
ArrayListen, welche Auto-Objekte enthalten.
Anwendungsbeispiel: Definition einer Klasse Polygon, die eine
Liste von Punkten enthält repräsentiert durch ArrayList<Point>.
Zeichnen im GraphicsWindow, Berechnung der Fläche.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-258
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Die Klasse ArrayList
Der Typ der Elemente einer ArrayList wird in spitze
Klammern gefasst: ArrayList<Point>
Dies soll uns momentan reichen, weiteres zu diesem Thema
folgt dann in Kapitel 12.
“generische Klassen”
ArrayList ist in Java mittlerweile die bessere Wahl im
Vergleich zu den allgemeineren, einfachen Arrays;
z.B. benötigen Arrays zur Laufzeit noch Typprüfungen.
for-Schleifen Notation kann genauso benutzt werden:
ArrayList<Point> al = new ArrayList<Point>();
al.add(new Point(1,2));
al.add(new Point(3,4));
for (Point p : al) {
System.out.print(p);
}
Schreibt: “java.awt.Point[x=1,y=2]java.awt.Point[x=3,y=4]”
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-259
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Zweidimensionale Arrays
Die Einträge eines Arrays können wieder Arrays sein. Das gibt ein
zweidimensionales Array.
int[][] einmaleins = new int[10][10];
for (int i = 0; i < 10; i++)
for (int j = 0; j < 10; j++)
einmaleins[i][j] = (i+1) * (j+1);
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-260
Arrays und . . .
Syntax, Typisierung, Semantik Methoden Algorithmen Objekte Mehrdimensional
Zusammenfassung Arrays
Ein Array ist eine Folge fester Länge von Variablen ein und
desselben Typs.
Arrays werden verwendet um Mengen und Listen von Daten zu
repräentieren.
Mit For-Schleifen kann der Reihe nach auf die Fächer eines
Arrays zugegriffen werden. Für lesenden Zugriff auf
Arrayfächer gibt es eine besondere Kurzform der For-Schleife
Teilweise gefüllte Arrays repräsentieren Listen variabler Größe.
Durch Neuallokieren und Umkopieren können diese auch
scheinbar beliebig vergrößert werden.
Die Klasse ArrayList stellt diese Funktionalität zur
Verfügung.
Zweidimensionale Arrays sind Arrays, deren Fächer selbst
wieder Arrays sind.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Arrays
6-261
Einführung in die Programmierung
mit Java
Teil 7: Objektorientiertes Design
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
24. November 2015
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-262
Inhalt Teil 7: Objektorientiertes Design
18
Fallstudie Game of Life
Zerlegung einer Problemstellung in Klassen
Entwurfsprinzip Model-View-Controller
19
Packages
20
Spezifikation von Methoden
21
Interfaces (Schnittstellen)
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-263
Objektorientiertes Programmierung
Idee Aufteilung eines komplexen Systems in möglichst
eigenständige Objekte.
Objekt vereinigt gedanklich zusammengehörige Daten & Methoden:
Daten oft auch Felder oder Attribute genannt, in Java
durch alle Instanzvariablen einer Klasse dargestellt
Methoden beschreiben Fähigkeiten des Objekts
In Java fassen wir ähnliche Objekte zu Klassen zusammen, im
Allgemeinen muss das jedoch nicht der Fall sein.
Wichtig ist größtmögliche Kapselung: Jedes Objekt modelliert eine
Sache und kümmert sich nur um eigenen Daten. Je kleiner die
Schnittstelle zu anderen Objekten, desto besser.
In Java erreichen wir dies mit dem Qualifikator private
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-264
Fallstudie Game of Life
Zerlegung einer Problemstellung in Klassen Entwurfsprinzip Model-View-Controller
Fallstudie: Game of Life
Erfunden 1970 von John Horton Conway.
Spielplan: unendliches 2D-Gitter (für uns: ∞ = 100 o.ä.)
Eine Zelle ist entweder lebendig oder tot.
Zu Beginn sind manche Zellen lebendig andere nicht.
In jeder Runde ändern sich die Zustände wie folgt:
Hat eine lebendige Zelle nur einen oder gar keine lebendigen
Nachbarn, so “stirbt” sie in der nächsten Runde.
Hat eine lebendige Zelle vier oder mehr lebendige Nachbarn so
“stirbt” sie ebenso in der nächsten Runde.
Hat eine tote Zelle genau drei lebendige Nachbarn, so wird sie
in der nächsten Runde lebendig.
Ansonsten ändert sich der Zustand der Zelle nicht.
Aufgabe: Simulation des Spiels
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-265
Fallstudie Game of Life
Zerlegung einer Problemstellung in Klassen Entwurfsprinzip Model-View-Controller
Benutzerschnittstelle
Das Spielfeld wird als m × n Gitter im Grafikfenster dargestellt.
Lebendige Zellen werden rot ausgefüllt, tote Zellen weiß.
Zu Beginn werden die lebendigen Zellen von dem Benutzer mit
der Maus angewählt.
Es werden 1000 Runden simuliert; der zeitliche Abstand der
Runden kann eingestellt werden.
Martin Hofmann, Steffen Jost
Runde
;
Einführung in die Programmierung
Objektorientiertes Design
7-266
Fallstudie Game of Life
Zerlegung einer Problemstellung in Klassen Entwurfsprinzip Model-View-Controller
Benutzerschnittstelle
Das Spielfeld wird als m × n Gitter im Grafikfenster dargestellt.
Lebendige Zellen werden rot ausgefüllt, tote Zellen weiß.
Zu Beginn werden die lebendigen Zellen von dem Benutzer mit
der Maus angewählt.
Es werden 1000 Runden simuliert; der zeitliche Abstand der
Runden kann eingestellt werden.
1
3
1
2
1
5
3
3
Martin Hofmann, Steffen Jost
2
3
2
2
1
2
2
1
Runde
;
Einführung in die Programmierung
1
1
3
2
2
4
4
2
1
2
3
3
1
2
2
1
Objektorientiertes Design
7-266
Fallstudie Game of Life
Zerlegung einer Problemstellung in Klassen Entwurfsprinzip Model-View-Controller
Grundlegende Designprinzipien
Mögliche Kandidaten für Klassen sind die Substantive der
Problembeschreibung.
Mögliche Kandidaten für Methoden sind die Verben der
Problembeschreibung.
Klassen sollen nicht zu eng gekoppelt sein (nicht
“uses”-Beziehungen zwischen je zwei Klassen)
Klassen sollen nicht zu groß werden; meist nicht mehr als 1-2
Bildschirmseiten.
Großzügig Klassen einführen, keine falsche Sparsamkeit.
Auf bekannte Entwurfsmuster zurückgreifen.
Design immer wieder verbessern;
Architektur mutig umkrempeln
Martin Hofmann, Steffen Jost
code smells → refactoring
Einführung in die Programmierung
Objektorientiertes Design
7-267
Fallstudie Game of Life
Zerlegung einer Problemstellung in Klassen Entwurfsprinzip Model-View-Controller
Architektur der Fallstudie
Eine Klasse, die die Parameter (Geometrie, Schnelligkeit,
Anzahl der Runden) definiert. Nur statische Instanzvariablen.
Eine Klasse für Positionen (nicht veränderbar, immutable):
Instanzvariablen: x, y.
Methoden: x, y abrufen.
Eine Klasse für Zellen (nicht veränderbar, immutable):
Instanzvariablen: Position, Zustand (tot/lebendig)
Methoden: Zustand und Position abfragen.
Eine Klasse für das Spielfeld:
Instanzvariablen: 2D Array von Zellen
Methoden: Zelle an einer Position abrufen; Zelle setzen;
Nachbarpositionen berechnen.
Eine Klasse für den Spieler (“die Natur”):
Instanzvariablen: Spielfeld, Ansicht.
Methoden: eine Runde durchführen:
Eine Klasse für die Ansicht:
Instanzvariablen: GraphicsWindow
Methoden: Spielfeld & Zelle zeichnen, Position per Click.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-268
Fallstudie Game of Life
Zerlegung einer Problemstellung in Klassen Entwurfsprinzip Model-View-Controller
Immutable Objects
Objekte, deren Zustand sich nie ändern kann, werden als
unveränderlich (engl. immutable) bezeichnet.
Z.B.: Falls alle Instanzvariablen der Klasse final sind und
wiederum ausschließlich auf immutable Objekte zeigen.
Vorteile
Vereinfacht. . .
. . . Verständnis und Beweis der Korrektheit
Klasseninvariante muss nur im Konstruktor geprüft werden
. . . direkte Wiederverwendung der Objekte
defensives Kopieren unnötig
. . . Verwendung in Containern und als Schlüssel in Tabellen
. . . Nebenläufige und Parallele Ausführung
. . . Garbage Collection
⇒ Vieles davon wird erst durch spätere Kapitel klar werden.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-269
Fallstudie Game of Life
Zerlegung einer Problemstellung in Klassen Entwurfsprinzip Model-View-Controller
Entwurfsprinzip MVC
(Model-View-Controller)
Die Aufgaben sind auf drei Komponenten verteilt:
Modell, hier das Spielfeld.
Verantwortlich für die Datenhaltung.
View, hier die Ansicht.
Verantwortlich für die (grafische) Darstellung der Daten.
Controller, hier der Spieler.
Verantwortlich fuer das Verarbeiten von Aktionen. Verändert
das Modell entsprechend und veranlasst Darstellung der
Änderungen bei der Ansicht.
Dieses Architekturprinzip hat sich sehr lange bewährt;
nicht davon abweichen!
Glaubt man, die spezielle Problemstellung erfordere eine andere
Architektur, so liegt oft nur ein Mangel an Erfahrung vor.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-270
Fallstudie Game of Life
Zerlegung einer Problemstellung in Klassen Entwurfsprinzip Model-View-Controller
Umgang mit Zellen am Rand des Spielfelds
zur Ermittlung des Folgezustands einer Zelle müssen alle acht
Nachbarn (N, NO, O, SO, S, SW, W, NW) besucht werden.
Zellen am Rand des Spielfelds haben aber nur 3 Nachbarn,
noch dazu in jeweils unterschiedlichen Richtungen.
Vergisst man das, so greift man auf nicht existierende
Arrayfächer zu ⇒ Programmabsturz.
Explizite Behandlung aller Randfälle mit Fallunterscheidungen
ist lästig.
Aus der Spieleprogrammierung sind zwei Standardlösungen des
Problems bekannt:
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-271
Fallstudie Game of Life
Zerlegung einer Problemstellung in Klassen Entwurfsprinzip Model-View-Controller
Berechnung der Nachbarn
Lösung 1: Spezielle Randfelder
Die Randzellen bleiben immer tot; sie werden auch nicht angezeigt
und ihr Folgezustand wird nicht neu berechnet. Sie fungieren nur
als fiktive Nachbarn des Randes des sichtbaren Felds.
Lösung 2: Wrap-Around
Der linke Nachbar einer Zelle am linken Rand ist per Definition der
rechts gegenüberliegende. Mit anderen Worten werden linker und
rechter Rand verklebt, sowie auch der obere und untere.
Ein rechteckiges Spielfeld hat nach dieser Verklebung dann die
Form eines Torus (entspricht Schwimmreifen oder Donut).
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-272
Fallstudie Game of Life
Zerlegung einer Problemstellung in Klassen Entwurfsprinzip Model-View-Controller
Implementierung der Fallstudie
Wir implementieren die Fallstudie während der Vorlesung.
Die endgültige Version wird anschließend auf der Vorlesungsseite
bereitgestellt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-273
Packages
Packages
Mehrere Klassen können in Java zu einem Paket (engl. package)
zusammengefasst werden.
Jede Datei des Pakets muss mit package <paketname>;
beginnen, wobei <paketname> der Paketname ist.
Die Klassen des Pakets müssen alle in einem Unterverzeichnis
liegen, dessen Name dem Paketnamen entspricht.
Der Paketname sollten nur aus Kleinbuchstaben bestehen.
Da Paketnamen einzigartig sein sollen, wird empfohlen, die
umgekehrte Domain des Herstellers voranzustellen, z.B.
de.lmu.tcs.GraphicsWindow
Jeder Punkt im Paktenamen entspricht einem Verzeichnis.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-274
Packages
Kompilieren von Packages
Der Java Kompiler erwartet Einhaltung der Verzeichnisstrukturen:
Wenn z.B. die Klasse Main die Deklaration package de.lmu.tcs;
enthält, dann muss Main.java im Unterverzeichnis de/lmu/tcs/
gespeichert sein.
Entsprechend im Verzeichnis (Ordner, Directory) darüber
kompilieren:
./> javac ./de/lmu/tcs/Main.java
./> java de.lmu.tcs.Main
. bezeichnet aktuelles Verzeichnis; Windows: / durch \ ersetzen
Wie wir hier sehen, muss man sich auch zum Ausführen im
Verzeichnis darüber befinden, und die Klasse mit der main-Methode
mit Paketnamen angeben.
Meistens genügt Kompilieren der Datei mit der main-Methode, da
benutzte Dateien automatisch mitkompiliert werden.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-275
Packages
Import
Auf die Klassen des Pakets greift man durch Vorschalten von
<paketname>. zu: Klasse java.awt.Rectangle ist völlig
unabhängig von Klasse my.package.Rectangle
⇒ Pakete teilen den Namensraum auf!
Wenn man anfangs import <paketname>.*; schreibt, kann
man alle eindeutigen Klassennamen auch direkt ohne
vorangestellten Paketnamen verwenden.
Verzeichnisse implizieren dabei keine Hierarchie:
import java.awt.*; importiert nicht automatisch auch noch
import java.awt.color.*;
Klassen ohne explizite package-Deklaration befinden sich in
der default package. Seit Java 1.4. können solche nicht
innerhalb einer echten package verwendet werden.
Export Man kann Pakete in .jar-Dateien zusammenpacken, um
diese auszutauschen. Direkte Ausführung ebenfalls möglich.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-276
Packages
Sichtbarkeit und Pakete
Pakete unterstützen ebenfalls die Aufteilung eines komplexen
Systems durch Gruppierung der Einheiten.
Wird eine gewisse Funktionalität in verschiedenen
Softwareprojekten benötigt, so sollte man versuchen,
gemeinsame Teile in unabhängige Pakte aufzuteilen.
Die Klassen eines Paketes sollten daher möglichst
untereinander abgeschlossen sein, so dass man jedes Paket
einzeln betrachten und verwenden kann.
⇒ Pakete sollten sich also nicht gegenseitig voraussetzen!
Dies wird durch die Qualifikatoren unterstützt:
Ist eine Klasse, Methode oder Instanzvariable weder public noch
private, so ist sie nur innerhalb ihres Pakets sichtbar.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-277
Spezifikation von Methoden
Spezifikation von Methoden: Vor- und
Nachbedingungen
Man kann das Verhalten von Methoden durch eine Vor- und
Nachbedingung (wie in der Hoare-Logik) beschreiben.
Dazu fügt man in der javadoc Dokumentation an geeigneter
Stelle eine Vorbedingung und eine Nachbedingung ein.
Die Vorbedingung bezieht sich auf die Werte der Parameter
der Methode und die Werte der Instanzvariablen vor
Ausführung der Methode.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-278
Spezifikation von Methoden
Nachbedingung
Die Nachbedingung bezieht sich auf die Werte der Parameter
(vor Ausführung der Methode), die Instanzvariablen nach
Ausführung der Methode und den Rückgabewert. Auf die
Werte der Instanzvariablen vor Ausführung der Methode kann
man auch Bezug nehmen durch entsprechende Kennzeichnung,
etwa durch _alt.
Vor- und Nachbedingungen sind für uns informell.
Es gibt Werkzeuge wie JML, in denen Vor- und
Nachbedingungen formalen Status haben und überprüft
werden.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-279
Spezifikation von Methoden
Beispiel
/** Abheben.
@param betrag Der abzuhebende Betrag.
Vorbedingung: kontostand >= betrag.
Nachbedingung: kontostand = kontostand_alt - betrag
@return Ob Abheben erfolgreich war
*/
public boolean abheben(double betrag) {
if (kontostand >= betrag){
kontostand = kontostand - betrag;
return true;
else return false;
}
Hinweis
Für Vor- und Nachbedingungen gibt es in javadoc leider keine
speziellen Tags
im Gegensatz zu @param, @return, etc.
Es gibt aber erweiterte Tools, welche dies anbieten.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-280
Spezifikation von Methoden
Spezifikation von Methoden und Klassen:
Invarianten
Oft soll eine bestimmte Bedingung an die Instanzvariablen von
jeder Methode der Klasse erhalten werden.
Statt diese in jede Vor- und Nachbedingung aufzunehmen kann
man solch eine Bedingung als Invariante der Klasse spezifizieren.
Jede Methode muss dann diese Invariante nach ihrer Ausführung
garantieren; im Gegenzug darf die Invariante vor Ausführung jeder
Methode angenommen werden.
/** Klasse fuer Bankkonten mit Ueberziehungsmoeglichkeit.
(Invariante: kontostand >= -limit).
*/
public class Bankkonto {
/** Der Kontostand. */
private double kontostand;
/** Ueberziehungslimit. */
private double limit;
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-281
Spezifikation von Methoden
Spezifikation von Klassen und Methoden:
Zusammenfassung
Beschreibung des Verhaltens von Methoden und Klassen kann
man in Vor- und Nachbedingungen, sowie Invarianten gliedern.
Die Vorbedingung einer Methode legt Bedingungen an ihren
Aufrufkontext fest. Die Methode ist immer so aufzurufen, dass
die Vorbedingung erfüllt ist.
Die Nachbedingung einer Methode spezifiziert den Zustand
des Objekts nach Aufruf der Methode. Die Methode ist so zu
implementieren, dass die Nachbedingung immer erfüllt ist,
vorausgesetzt die Vorbedingung war erfüllt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-282
Spezifikation von Methoden
Die Invariante einer Klasse ist eine implizite Vor- und
Nachbedingung für alle Methoden einer Klasse: Alle Methoden
(einschließlich der Konstruktoren) sind so zu implementieren,
dass die Invariante erhalten wird.
Umgekehrt kann man dann beim Implementieren einer
Methode voraussetzen, dass alle Objekte der Klasse die
Invariante erfüllen.
Vor- und Nachbedingungen sind für uns informell. Will man sie
formalisieren, so treten nichttriviale Schwierigkeiten auf
(exakter Geltungsbereich von Invarianten, Formulierung von
Bedingungen ohne Bezugnahme auf private Instanzvariablen,
. . . ).
Es existieren solche Formalisierungen, z.B.: JML, die zudem
das Erfülltsein von Bedingungen teilweise automatisch
überprüfen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-283
Interfaces (Schnittstellen)
Motivation
Verschiedene Objekte können gleiche Fähigkeiten besitzen, z.B.
zwei Objekte der gleichen Klasse haben gleiche Fähigkeiten.
Es kann aber auch sein, dass die Fähigkeiten zweier Objekte nur
teilweise überlappen: z.B. haben Rectangle, Dreieck und
Polygon alle eine Methode zur Berechnung Ihre Flächeninhalts.
Verschiedene Klassen mit gemeinsamen Fähigkeiten können wir in
Java mit einer Schnittstellen-Definition (engl. interface) zu einem
neuen Typen zusammenfassen.
Wenn wir ein Objekt mit einem Interface-Typen erhalten, so kennen
wir zwar nicht seine Klasse, aber wir kennen dennoch Methoden,
welche wir darauf anwenden können: die Methoden des Interface.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-284
Interfaces (Schnittstellen)
Schnittstellen
Interface-Definition sehr ähnlich zu Klassendefinition:
public interface hatFlaeche {
public static final double PI = 3.1514; // Konstante
public double berechneFlaeche();
// Abstrakte M.
public default double flaecheMalPi() { // Methode
double f = this.berechneFlaeche();
return f * PI;
} }
Unterschiede
Keine Instanzvariablen; nur Konstanten: static und final
Keine Konstruktoren
Alle Methoden müssen public sein
Methoden dürfen abstrakt bleiben, d.h. nur Angabe der
Typ-Signatur, kein Methodenrumpf! Ansonsten default
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-285
Interfaces (Schnittstellen)
Benutzung Interface-Typ
Interface hatFlaeche ist ein Typ, der überall dort verwendet
werden kann, wo Typen vorkommen:
Beispiel
Bekommt eine Methode einen Parameter des Typs hatFlaeche, so
können wir darauf nur die Methoden berechneFlaeche und
flaecheMalPi anwenden, oder die Konstante PI abfragen.
public double gesamtFlaeche(hatFlaeche[] besitzuemer) {
double result = 0;
for (hatFlaeche f : besitzuemer)
result = result + f.berechneFlaeche();
return result;
}
Wie man am Beispiel sieht, kann man auch Arrays über einem
Interface-Typen bilden. Die Elemente des Arrays können
verschiedenen Klassen angehören — aber alle Elemente haben die
Fähigkeiten von hatFlaeche!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-286
Interfaces (Schnittstellen)
Implementierung
Durch die Klausel implements NameDerSchnittstelle in einer
Klassendefinition wird angezeigt, dass Objekte dieser Klasse die
Schnittstelle implementieren.
Objekte so einer Klasse haben dann automatisch auch den Typ
NamederSchnittstelle.
Abstrakte Methoden müssen implementiert werden.
Default Methoden können mit @Override überschrieben
werden
gilt auch für statische Methoden
Konstanten dürfen überschattet werden.
Implementiert eine Klasse die Methoden einer Schnittstelle, fehlt
aber die implements-Klausel, so haben Objekte der Klasse nicht
den Typ der Schnittstelle.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-287
Interfaces (Schnittstellen)
Beispiel
public class Dreieck implements hatFlaeche {
private Point a; private Point b; private Point c;
public Dreieck(Point a, Point b, Point c) {...}
}
public double berechneFlaeche() {
double edgeab = a.distance(b);
double edgebc = b.distance(c);
double edgeca = c.distance(a);
double umfang = (edgeab + edgebc + edgeca)/2;
double heron = Math.sqrt(umfang *(umfang-edgeab)
*(umfang-edgebc)*(umfang-edgeca));
return heron;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-288
Interfaces (Schnittstellen)
Mehrfache Implementierung von
Schnittstellen
Ein Klasse darf auch gleich mehrere Schnittstellen implementieren,
wenn sie die Fähigkeiten dieser Schnittstellen jeweils bietet, z.B.
hatFlaeche und hatGewicht.
Beispiel: class Foo implements Bar, Baz, Qux Die Klasse
Foo hat also die Fähigkeiten der Interfaces Bar, Baz und Qux
Wichtig:
Falls es in diesen Interfaces zufälligerweise eine Methode mit
gleichem Namen und gleicher Typsignatur gibt, so muss die Klasse
Foo dieser Methode mit @Override überschreiben!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-289
Interfaces (Schnittstellen)
UML Notation für Interfaces
In UML zeichnet man von der Klasse zur Schnittstelle einen
gestrichelten Pfeil mit dreieckiger, hohler Spitze: −−−−B
Die Schnittstelle selbst wird wie eine Klasse dargestellt, aber
zusätzlich mit dem Schlüsselwort <<interface>> gekennzeichnet.
Alternative: Ball-Notation: Neben die Klasse zeichnet man eine
Linie, welche in einem Kreis endet, unter dem der Name des
Interface steht.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-290
Interfaces (Schnittstellen)
Ausblick: Functional Interfaces
Das Paket java.util.function stellt viele Functional
Interfaces bereit. Das sind Interfaces, welche nur eine einzige
Methode spezifizieren.
Beispiel
interface Comparable<T> {
int compareTo(T o); }
Idee
Wenn wir ein Array sortieren wollen, so ist der Sortier-Algorithmus
unabhängig davon, was wir sortieren — so lange wir die Elemente
nur miteinander vergleichen können.
Dieses Functional Interface spezifiziert die Menge aller Klassen, für
deren Objekte wir eine Methode zum Vergleichen haben.
Auf dieses wichtige Thema werden wir später noch einmal genauer
eingehen, und dann auch erklären, was <T> bedeutet.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-291
Interfaces (Schnittstellen)
Sprites
Anderes Beispiel:
public interface Sprite {
/** Zeichnen des Sprite in ein gegebenes
Rechteck */
public void zeichnen(GraphicsWindow g, Rectangle r);
}
Die Schnittstelle Sprite fasst Objekte zusammen, die “sich” in ein
gegebenes Rechteck zeichnen können.
Engl.: sprite = kleiner Waldgeist.
Bezeichnet kleine Figuren, die sich im Rahmen eines Computerspiels
oder einer Animation auf dem Bildschirm umherbewegen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Objektorientiertes Design
7-292
Einführung in die Programmierung
mit Java
Teil 8: Vererbung
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
1. Dezember 2015
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-293
Inhalt Teil 8: Vererbung
22
23
24
Motivation
Unterklassen einer Klasse
Vererbung von Instanzvariablen und
Methoden
Instanzvariablen
Konstruktoren
Methoden
25
Subtyping
Klassenhierarchie
Dynamische Bindung
Klasse Object
Übungen
26
27
28
Vererbung und Spezifikation
Abstrakte Methoden
Zusammenfassung Vererbung
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-294
Motivation
Motivation
Oft hat man sehr ähnliche Klassen, welche ähnlichen Code
enthalten. Die Duplikation von Code ist aber generell schlecht
(erschwert Wartung, Korrektheitsbeweise, etc.).
Java bietet Vererbung an, um die Funktionalität einer Klasse ohne
Duplikation von Code zu erweitern oder auch zu spezialisieren.
Die Erben einer Klasse behalten die Funktionalität des Vorfahren,
aber können neue Funktionalität anbieten.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-295
Motivation
Beispiel: Bankkonto
Wdh.
public class Bankkonto {
private double kontostand;
private String besitzer;
public Bankkonto() { this("Steffen", 5.0); }
public Bankkonto(String besitzer, double betrag) {
kontostand
= betrag;
this.besitzer = besitzer; }
public double getKontostand() { return kontostand; }
public boolean istUeberzogen() { return kontostand < 0.0; }
public void einzahlen(double betrag){ kontostand += betrag; }
public void abheben (double betrag){ kontostand -= betrag; }
public void ueberweisen(double betrag, Bankkonto empfaenger){
this.abheben(betrag);
empfaenger.einzahlen(betrag);
} }
Hinweis: Am Anfang eines Konstruktors ruft this(. . . ) einen
anderen Konstruktor der Klasse mit anderen Argumenten auf.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-296
Motivation
Beispiel: Bankkonto beerben
public class Sparkonto extends Bankkonto {
/* Neue Instanzvariablen, z.B. Zinssatz */
/* Neue Methoden, z.B. Zinsen berechnen */
/* Neue Konstruktoren, z.B. mit Angabe des Zinssatzes */
}
Alle bisherigen Methoden bleiben verfügbar, als hätte man sie
in die Definition von Sparkonto() hineinkopiert.
Bisherige private Instanzvariablen sind ebenfalls vorhanden,
sind aber nicht sichtbar in hier neu definierten Methoden.
Für Konstruktoren gelten besondere Regeln.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-297
Motivation
Beispiel: Sparkonto
Ein Sparkonto ist ein Bankkonto mit zusätzlicher Funktionalität:
public class Sparkonto extends Bankkonto {
private double zinssatz;
public Sparkonto(double satz) {
zinssatz = satz;
}
}
public void zinsenAnrechnen() {
double zinsen=this.getKontostand()*zinssatz/100.0;
this.einzahlen(zinsen); // Aufruf geerbte Methode
}
Man könnte hier überall “this.” auch weglassen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-298
Motivation
Verwendung
Sparkonto johannasSpar = new Sparkonto(1.25);
johannasSpar.einzahlen(200.0);
johannasSpar.zinsenAnrechnen();
System.out.println("Johannas Sparkonto: " +
johannasSpar.getKontostand());
Ausgabe:
Johannas Sparkonto: 207.5625
Erklärung: (200 + 5) · (1 + 1.25
100 ) = 207.5625
Der Konstruktor Bankkonto() setzt den Kontostand auf 5.00
(Geschenk der Sparkasse); der geerbte Konstruktor wird also auch
noch aufgerufen!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-299
Motivation
Funktionsweise
Beim Aufruf
johannasSpar.zinsenAnrechnen();
wird folgender Code ausgeführt:
double zinsen = johannasSpar.getKontostand() *
zinssatz / 100.0;
johannasSpar.einzahlen(zinsen);
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-300
Unterklassen einer Klasse
Terminologie
Bankkonto ist die Oberklasse (superclass) von Sparkonto.
Sparkonto erbt von (inherits from) Bankkonto.
Sparkonto ist eine Unterklasse (subclass) von Bankkonto.
In Java hat jede Klasse nur genau eine Oberklasse.
Eine Klasse kann aber mehrere Unterklassen haben.
Beispiel: Bankkonto könnte noch weitere Unterklassen haben, wie
etwa Girokonto, Tagesgeldkonto, etc.
Auch von Unterklassen kann man weiter vererben. Zum Beispiel ein
Prämiensparkonto als Unterklasse von Sparkonto, etc.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-301
Unterklassen einer Klasse
UML-Notation
Ein Pfeil mit hohler Dreieckspitze −. von Unterklasse zu
Oberklasse zeigt Vererbung im UML-Klassendiagramm an:
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-302
Vererbung von
Instanzvariablen Konstruktoren Methoden
Vererbung von Instanzvariablen
Alle Instanzvariablen der Oberklasse werden vererbt;
private-Instanzvariablen sind in der Unterklasse vorhanden,
aber nicht mehr sichtbar!
Allerdings können ererbte Methoden der Oberklasse auf alle
vererbten Instanzvariablen zugreifen!
Beispiel Ein Sparkonto hat also die Instanzvariablen kontostand,
besitzer und zinssatz. Neu geschriebene Methoden können aber
nur zinssatz direkt beeinflussen.
Hinweis Man kann eine Instanzvariable als protected
kennzeichnen. Dann ist sie auch in den Unterklassen sichtbar (und
in allen Klassen derselben package). Von Verwendung wird aus
diversen Gründen abgeraten!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-303
Vererbung von
Instanzvariablen Konstruktoren Methoden
Konstruktoren der Unterklasse
Konstruktoren werden nicht vererbt.
In einem Konstruktor kann aber ein Konstruktor der
Oberklasse mit super(. . . ) aufgerufen werden.
Man sollte einen Konstruktor der Oberklasse immer zu Beginn des
Konstruktors der Unterklasse aufrufen, damit die ererbten privaten
Instanzvariablen geeignet initialisiert werden!
Beispiel:
public Sparkonto(String besitzer, double satz) {
super(besitzer, 0.0);
zinssatz = satz;
}
Geschieht dies nicht (wie im ursprünglichen Sparkonto-Beispiel),
so wird der Defaultkonstruktor (ohne Parameter) der Oberklasse
automatisch eingefügt. Gibt es keinen Defaultkonstruktor in der
Oberklasse er, so gibt es einen Compilerfehler!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-304
Vererbung von
Instanzvariablen Konstruktoren Methoden
Überschreiben
public-Methoden werden vererbt.
private-Methoden werden nicht vererbt; geerbte
public-Methoden rufen diese aber weiterhin auf.
Geerbte Methoden können überschrieben werden
wie bei Interface-Implementierung
Überschreiben
Definiert man in der Unterklasse eine Methode, die es in der
Oberklasse schon gibt (Name und Parameterzahl und -typen
stimmen überein), so wird die ererbte Methode überschrieben
(engl. overriding). Die neudefinierte Methode ersetzt die alte
überall, d.h. auch in unveränderten geerbten Methoden (!!!),
welche die überschriebene Methode verwenden.
Es empfiehlt sich erneut, die @Override Annotation vor der
Definition eine überschriebenen Methode zu schreiben, damit der
Kompiler dies prüfen kann.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-305
Vererbung von
Instanzvariablen Konstruktoren Methoden
Beispiel: Girokonto
public class Girokonto extends Bankkonto {
private int anzahlTransaktionen;
private double gebuehrensatz;
public Girokonto(double satz) {
super();
anzahlTransaktionen = 0;
gebuehrensatz = satz;
}
@Override
public void einzahlen(double betrag) {
super.einzahlen(betrag);
anzahlTransaktionen++;
}
..
.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-306
Vererbung von
Instanzvariablen Konstruktoren Methoden
..
.
@Override
public void abheben(double betrag) {
super.abheben(betrag);
anzahlTransaktionen++;
}
}
public void gebuehrenAbziehen() {
double gebuehren = anzahlTransaktionen
* gebuehrensatz;
super.abheben(gebuehren);
anzahlTransaktionen = 0;
}
Jede Ein-/Auszahlung erhöht nun einen internen Zähler!
super ermöglicht Aufruf der geerbten Methode.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-307
Vererbung von
Instanzvariablen Konstruktoren Methoden
Bemerkungen
Die Pseudovariable super bezeichnet das aktuelle Objekt
aufgefasst als Objekt der Oberklasse.
super.super.foo() ist nicht erlaubt!
⇒ Jede Klasse hat nur eine Oberklasse!
Hätten wir in gebuehrenAbziehen statt
super.abheben(betrag); einfach nur
this.abheben(gebuehren); geschrieben, so wären für das
Abziehen der Gebühren gleich wieder welche fällig geworden.
. . . was vermutlich Realität entspricht?!?
Wir können in Girokonto nicht schreiben
kontostand = kontostand - gebuehren;
da kontostand nicht sichtbar ist.
Wir könnten natürlich eine neue Instanzvariable mit Namen
kontostand deklarieren, dann hätte aber jedes Objekt zwei
verschiedene Instanzvariablen des Namens kontostand
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-308
Subtyping
Klassenhierarchie Dynamische Bindung Klasse Object Übungen
Subtyping
Ein Objekt der Unterklasse kann immer dort verwendet werden, wo
ein Objekt der Oberklasse erwartet wird.
Ganz analog zu Interface-Typen!
Beispiel:
Sparkonto johannasSpar = new Sparkonto(1.25);
Bankkonto matthiasGiro = new Bankkonto();
matthiasGiro.ueberweisen(200, johannasSpar);
Zur Erinnerung:
public void ueberweisen(double betrag,Bankkonto empfaenger)
Zwischen der Unterklasse und der Oberklasse besteht eine
“ist-ein”-Beziehung (engl. “is a”-relationship).
Beispiel: Bankkonto-Array kann auch Sparkonten enthalten!
In den Anwendungen ergibt sich durch Subtyping oft eine komplexe
Klassenhierarchie.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-309
Subtyping
Klassenhierarchie Dynamische Bindung Klasse Object Übungen
Klassenhierarchie in UML
Ein Pfeil mit hohler Dreieckspitze −. von Unterklasse zu
Oberklasse zeigt Vererbung im UML-Klassendiagramm an:
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-310
Subtyping
Klassenhierarchie Dynamische Bindung Klasse Object Übungen
Geschachtelte Vererbung
Ist B eine Unterklasse von A, so kann man ohne weiteres eine
Unterklasse C von B definieren.
Jedes Objekt der Klasse C ist dann automatisch ein Objekt der
Klasse B und als solches auch ein Objekt der Klasse A.
Die Oberklasse von C ist aber nur B.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-311
Subtyping
Klassenhierarchie Dynamische Bindung Klasse Object Übungen
Beispiel: Festgeldkonto
public class Festgeldkonto extends Sparkonto {
private int
restJahre;
private double strafgebuehr;
@Override
public void zinsenBerechnen() {
restJahre = restJahre-1;
super.zinsenAnrechnen();
}
@Override
public void abheben(double betrag) {
if (restJahre > 0)
super.abheben(strafgebuehr);
super.abheben(betrag);
}
public Festgeldkonto(int jahre,int gebuehr,double zinssatz){
super(zinssatz);
strafgebuehr = gebuehr;
restJahre = jahre;
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-312
Subtyping
Klassenhierarchie Dynamische Bindung Klasse Object Übungen
Dynamische Bindung
Jedes Objekt gehört trotz Subtyping zu einer festen Klasse. Welche
Methode bei einem Objekt aufgerufen wird, richtet sich immer nach
seiner echten Klasse, auch dann, wenn es per Subtyping als Objekt
der Oberklasse benutzt wird. Dies bezeichnet man als dynamische
Bindung (engl. dynamic dispatch).
Beispiel
Bankkonto matthiasGiro = new Girokonto(0.25);
Sparkonto johannasSpar = new Sparkonto(200.0);
johannasSpar.ueberweisen(100.0, matthiasGiro);
Hier fallen bei matthiasGiro Gebühren an, obwohl die Methode
einzahlen in Bankkonto eigentlich keine Gebühren beaufschlagt!
Es wird aber die überschriebene Methode verwendet!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-313
Subtyping
Klassenhierarchie Dynamische Bindung Klasse Object Übungen
Typecast und instanceof
Hat ein Ausdruck den Typ einer Oberklasse, so kann man ihn
explizit auf die Unterklasse konvertieren:
Bankkonto matthiasGiro = new Girokonto(0.25);
Girokonto y = (Girokonto)matthiasGiro;
Wird eine Typkonversion (type cast) der Form (A)e ausgewertet,
so wird überprüft, ob die tatsächliche Klasse von e Unterklasse
(evtl. über mehrere Ecken) von A ist:
Falls ja: Auswertung wird fortgesetzt
Falls nein: Programmabbruch
Der Typ des Ausdrucks “(A)e” ist A
Der Ausdruck “e instanceof A” hat den Wert true genau dann,
wenn die tatsächliche Klasse von e unterhalb von A liegt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-314
Subtyping
Klassenhierarchie Dynamische Bindung Klasse Object Übungen
Die Klasse Object
Die Klasse Object steht an der Spitze jeder Klassenhierarchie.
Sie definiert die Methoden
String toString()
/* in einen String konvertieren */
boolean equals(Object other) /* auf Gleichheit testen */
Object clone()
/* Kopie des Objektes anlegen */
Diese Methoden kann man in einer Klasse überschreiben.
Dies sollte man auch tun, damit diese Methoden immer sinnvoll
funktionieren!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-315
Subtyping
Klassenhierarchie Dynamische Bindung Klasse Object Übungen
Übungen I
Was ist b.getKontostand() am Ende ?
Sparkonto b = new Sparkonto(10);
b.einzahlen(4995);
b.abheben(b.getKontostand() / 2);
b.zinsenAnrechnen();
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-316
Subtyping
Klassenhierarchie Dynamische Bindung Klasse Object Übungen
Übungen II
Was sollte hier Unterklasse sein, was Oberklasse:
Angestellter-Vorgesetzter
Polygon- Dreieck
Doktorstudent-Student
Person-Student
Angestellter-Doktorstudent
Bankkonto-Girokonto
Fahrzeug-Pkw
Fahrzeug-Van
Pkw-Van
Lkw-Fahrzeug
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-317
Subtyping
Klassenhierarchie Dynamische Bindung Klasse Object Übungen
Übungen III
Die Klasse Sub sei Unterklasse von Sandwich. Welche der
folgenden Zuweisungen sind erlaubt?
Sandwich x = new Sandwich();
Sub y = new Sub();
x = y;
y = x;
y = new Sandwich();
x = new Sub();
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-318
Subtyping
Klassenhierarchie Dynamische Bindung Klasse Object Übungen
Übungen IV
class A {
public void exec()
public void doIt()
}
class B extends A {
public void doit()
}
class C extends B {
public void doIt()
}
{ this.doIt(); }
{ System.out.println("A"); }
{ System.out.println("B"); }
{ System.out.println("C"); }
Was wird ausgegeben bei folgendem Code?
A a = new B();
B b = new C();
a.exec();
b.exec();
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-319
Vererbung und Spezifikation
Behavioural Subtyping
Hat man in einer Oberklasse Methoden durch Vor- und
Nachbedingungen spezifiziert, so sollten überschreibende Versionen
davon auch dieser Spezifikation genügen.
Der Vorteil ist dann, dass Spezifikationen für sämtliche geerbte
Methoden fortgelten.
Ebenso sollten Invarianten, die in der Oberklasse vereinbart wurden,
auch von den Methoden der Unterklasse eingehalten werden.
Liegen all diese Bedingungen vor, so sagt man, die Unterklasse sei
ein behavioural subtype der Oberklasse.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-320
Vererbung und Spezifikation
Verstärkung von Invarianten bei Vererbung
Eine Unterklasse kann eine stärkere Invariante verlangen als die
Oberklasse
z.B. positive breite & hoehe müssen auch noch gleich sein
Die Vererbungsregeln für Konstruktoren unterstützen dies.
Beispiel: Quadrat als Unterklasse von Rechteck
public class Rechteck {
private int x, y, breite, hoehe;
public Rechteck(int x, int y, int breite, int hoehe) {
this.x=x;this.y=y;this.breite=breite;this.hoehe=hoehe;
}
public void verschieben(int dx, int dy) {
x=x+dx;y=y+dy;
}}
public class Quadrat extends Rechteck {
public Quadrat(int x, int y, int seite) {
super(x,y,seite,seite);
}}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-321
Vererbung und Spezifikation
Verstärkung von Invarianten bei Vererbung
Eine Unterklasse kann eine stärkere Invariante verlangen als die
Oberklasse
z.B. positive breite & hoehe müssen auch noch gleich sein
Die
Vererbungsregeln
für Konstruktoren unterstützen dies.
Mögliches
Problem:
Was, wenn
Rechteck
Änderung
von
breite und hoehe erlaubt?
Beispiel:
Quadrat
als Unterklasse
von
Rechteck
public class Rechteck {
private int x, y, breite, hoehe;
public Rechteck(int x, int y, int breite, int hoehe) {
this.x=x;this.y=y;this.breite=breite;this.hoehe=hoehe;
}
public void verschieben(int dx, int dy) {
x=x+dx;y=y+dy;
}}
public class Quadrat extends Rechteck {
public Quadrat(int x, int y, int seite) {
super(x,y,seite,seite);
}}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-321
Abstrakte Methoden
Abstrakte Klassen und Methoden
Man kann eine Methodendeklaration in einer Klasse mit dem
Schlüsselwort abstract kennzeichnen und keine Implementierung
angeben.
Die gesamte Klasse muss dann auch das Schlüsselwort abstract
tragen. Man kann dann von dieser Klasse keine Instanzen erzeugen
(new ist also verboten).
Allerdings kann man von der Klasse erben und dann die
“abstrakten” Methoden konkret implementieren.
Verbleiben abstrakte Methode, muss die Klasse abstrakt bleiben.
Abstrakte Klassen wurden inzwischen von Interfaces weitestgehend
abgelöst. Ein Interface bietet sich meist an, wenn Instanzvariablen
für die konkreten Methoden irrelevant sind.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-322
Abstrakte Methoden
Beispiel AbstraktesBankkonto I
public abstract class AbstraktesBankkonto {
private static int naechsteNummer = 0;
private int kontonummer;
public AbstraktesBankkonto() {
kontonummer = naechsteNummer;
naechsteNummer++;
}
public int getKontonummer() {
return kontonummer;
}
public abstract void writeLog(String s);
..
.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-323
Abstrakte Methoden
Beispiel AbstraktesBankkonto I
..
.
public void abheben(double betrag) {
writeLog("Abhebung: "+betrag);
}
public void einzahlen(double betrag) {
writeLog("Einzahlung: "+betrag);
}
}
public void ueberweisen(AbstraktesBankkonto b,double betrag){
this.abheben(betrag);b.einzahlen(betrag);
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-324
Abstrakte Methoden
Beispiel AbstraktesBankkonto II
public class Bankkonto extends AbstraktesBankkonto {
private double kontostand;
public double getKontostand() {
return kontostand;
}
public Bankkonto() {
super();
kontostand = 0;
}
..
.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-325
Abstrakte Methoden
..
.
}
@Override
public void writeLog(String s) {
System.out.println
("Kontonr.: " + getKontonummer() + "\n" +
s +
"\nNeuer Kontostand: " + getKontostand());
}
@Override
public void einzahlen(double betrag) {
kontostand += betrag;
super.einzahlen(betrag);
}
@Override
public void abheben(double betrag) {
kontostand -= betrag;
super.abheben(betrag);
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-326
Abstrakte Methoden
Verwendung
b.einzahlen(3);
c.einzahlen(6);
b.ueberweisen(c,4);
Ausgabe:
Kontonr.: 0
Einzahlung: 3.0
Neuer Kontostand:
Kontonr.: 1
Einzahlung: 6.0
Neuer Kontostand:
Kontonr.: 0
Abhebung: 4.0
Neuer Kontostand:
Kontonr.: 1
Einzahlung: 4.0
Neuer Kontostand:
Martin Hofmann, Steffen Jost
3.0
6.0
-1.0
10.0
Einführung in die Programmierung
Vererbung
8-327
Zusammenfassung
Zusammenfassung: Vererbung
Man kann zu einer gegebenen Klasse Unterklassen definieren:
class
neue
neue
neue
NameDerUnterklasse extends NameDerOberKlasse{
Konstruktoren
Instanzvariablen
Methoden}
Instanzvariablen, Methoden der Oberklasse werden geerbt.
Private Instanzvariablen der Oberklasse sind in der Unterklasse
nicht sichtbar.
public und protected Methoden der Oberklasse kann man
überschreiben. Die neue Definition ersetzt die alte überall,
auch in Definitionen von Methoden der Oberklasse.
Mit super kann man auf Methoden und Konstruktoren der
Oberklasse zugreifen.
Konstruktoren werden nicht vererbt.
Ausnahme: Defaultkonstruktor ohne Argumente.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-328
Zusammenfassung
Vererbung wird benutzt um “is a”-Beziehungen
softwaretechnisch zu realisieren.
Die UML-Notation für Vererbung ist ein −. Pfeil von der
Unter- zur Oberklasse.
Object ist automatisch Oberklasse jeder Klasse. Die
Methoden equals, toString der Klasse Object können
überschrieben werden.
Behavioural subtyping: Spezifikationen aus der Oberklasse
werden von der Unterklasse respektiert.
Abstrakte Methoden in abstrakten Klassen werden nicht
implementiert, sie müssen in konkreten Unterklassen
überschrieben werden.
Der Einsatz von Interfaces hat inzwischen Vererbung in vielen
Fällen ersetzt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Vererbung
8-329
Einführung in die Programmierung
mit Java
Teil 9: Ausnahmebehandlung
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
8. Dezember 2015
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-330
Inhalt Teil 9: Ausnahmebehandlung
29
Syntax von Ausnahmen
Geprüfte Ausnahmen
Selbstdefinierte Ausnahmen
30
Abfangen von Ausnahmen
31
Das Entwurfsmuster Singleton
32
Zusammenfassung
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-331
Syntax
Geprüfte Ausnahmen Selbstdefinierte Ausnahmen
Beispiel
public static int fakt(int x) {
if (x < 0)
throw new IllegalArgumentException
("fakt: Negatives Argument " + x);
else { ... }
}
Die Ausführung von System.out.println(fakt(-2)) führt zu
Exception in thread "main" \
java.lang.IllegalArgumentException: \
fakt: Negatives Argument -2
at Fakt.fakt(Fakt.java:7)
at Fakt.main(Fakt.java:3)
Process Fakt exited abnormally with code 1
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-332
Syntax
Geprüfte Ausnahmen Selbstdefinierte Ausnahmen
Was passiert hier?
Durch das throw statement wird eine Ausnahme, hier
IllegalArgumentException geworfen.
Die Ausnahme ist ein spezielles Objekt, das wie üblich mit new
erzeugt werden kann.
Einer der Konstruktoren der verwendeten Ausnahme hat ein
Argument vom Typ String.
Das Werfen der Ausnahme bricht die Programmabarbeitung
sofort ab.
Es kommt zu einer Fehlermeldung aus der die Art der
geworfenen Ausnahme, ihr String-Parameter, sowie der Ort
(Klasse, Methode, Programmzeile) ihres Auftretens hervorgeht.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-333
Syntax
Geprüfte Ausnahmen Selbstdefinierte Ausnahmen
Vordefinierte Ausnahmen
Alle Ausnahmen gehören zur (vordefinierten) Klasse
Exception oder einer Unterklasse.
Exception selbst ist Unterklasse von Throwable. Das
“Argument” der throw-Anweisung muss Throwable sein.
Unterklassen von Exception sind IOException,
ClassNotFoundException,
CloneNotSupportedException,RuntimeException, u.v.a.m.
Unterklassen von IOException sind EOFException und
FileNotFoundException u.v.a.m.
Unterklassen von RuntimeException sind
IllegalArgumentException, IndexOutOfBoundsException
u.v.a.m.
Unterklasse von IllegalArgumentException ist z.B.:
NumberFormatException
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-334
Syntax
Geprüfte Ausnahmen Selbstdefinierte Ausnahmen
Geprüfte Ausnahmen
Manche Ausnahmen sind überprüft (checked), andere nicht
(unchecked).
Überprüfte Ausnahmen müssen entweder aufgefangen werden
(kommt später) oder explizit als möglich deklariert werden:
import java.io.*;
...
public void m() {
throw new IOException("");
}
Kompilieren führt zu folgender Fehlermeldung:
/home/mhofmann/work/teaching/EiP/Fakt.java:19:\
unreported exception java.io.IOException; must \
be caught or declared to be thrown
throw new IOException("");
^
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-335
Syntax
Geprüfte Ausnahmen Selbstdefinierte Ausnahmen
Deklaration überprüfter Ausnahmen
Kann in einer Methode eine überprüfte Ausnahme auftreten und
wird sie nicht aufgefangen, so muss sie mit throws deklariert
werden:
import java.io.*;
...
public void m() throws IOException{
throw new IOException("");
}
Die “Philosophie” ist, dass Ausnahmen, deren Auftreten auf einen
Programmierfehler hindeutet, nicht überprüft werden.
Ausnahmen, deren Auftreten von Zeit zu Zeit unvermeidlich ist
(FileNotFoundException, sind dagegen überprüft.
RuntimeException und ihre Unterklassen sind nicht überprüft; alle
anderen sind überprüft.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-336
Syntax
Geprüfte Ausnahmen Selbstdefinierte Ausnahmen
Deklaration eigener Ausnahmen
public static int fakt(int x) {
if (x < 0)
throw new FakultaetAusnahme
("fakt: Negatives Argument " + x);
else { ... }
}
public class FakultaetAusnahme extends RuntimeException {
public FakultaetAusnahme() {}
public FakultaetAusnahme(String grund)
{
super(grund);
}
}
Ausnahmen werden also genauso wie Klassen deklariert und sind
ganz normale Objekte.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-337
Abfangen von Ausnahmen
Abfangen von Ausnahmen
Mit try catch werden Ausnahmen abgefangen.
public static void main(String[] args) {
Scanner konsole = new Scanner(System.in);
try {
System.out.println("Bitte die Zahl");
String input = konsole.nextLine();
int zahl = Integer.parseInt(input);
System.out.println("Fakt("+zahl+")="+fakt(zahl));
}
catch (NumberFormatException ausnahme) {
System.out.println("Falsche Eingabe.");
}
catch (IllegalArgumentException e) {
System.out.print(e.getMessage());
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-338
Abfangen von Ausnahmen
Abfangen von Ausnahmen
Wird im try Block eine Ausnahme geworfen, so werden der
Reihe nach alle catch Blöcke (handler) durchgegangen.
Der erste Handler, der zur geworfenen Ausnahme passt,
kommt zur Anwendung. Sein formaler Parameter wird an das
geworfene Ausnahmeobjekt gebunden und der entsprechende
Code wird ausgeführt.
Man sollte darauf achten, dass Handler die Ausnahmen
sinnvoll bearbeiten.
catch (Exception e) {}
ist nicht sinnvoll.
Jede Ausnahme versteht die Methode printStackTrace().
Dies gibt die Folge der Methodenaufrufe bis zu ihrer Auslösung
auf der Konsole aus.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-339
Abfangen von Ausnahmen
Die finally-Klausel
Es kann passieren, dass bestimmte Anweisungen auf jeden Fall
durchgeführt werden müssen, auch wenn eine Ausnahme geworfen
wird.
Man könnte dann die Ausnahme fangen, die bestimmten
Anweisungen ausführen und dann die Ausnahme gleich wieder
werfen.
Alternativ gibt es das finally-Konstrukt.
Beispiel:
try {
/* Alle Kombinationen der Reihe nach
durchprobieren. */
}
finally {
/* Abhauen */
}
Im [Horstmann] stehen geringfügig sinnvollere Beispiele.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-340
Singleton-Pattern
Das Singleton Pattern
Manchmal möchte man von einer Klasse nur eine einzige Instanz
erzeugen.
Beispiel: Ausnahmen bei der Chipkartenprogrammierung.
Man kann dann zu Beginn ein Objekt erzeugen mit einer Methode,
die die gewünschte Instanz liefert. Ist noch keine erzeugt, so wird
eine erzeugt, ansonsten die bereits erzeugte zurückgeliefert.
Chipkarten (JavaCard) haben keine Garbage Collection; man muss
daher vermeiden, zuviele Ausnahmeobjekte zu erzeugen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-341
Singleton-Pattern
Durchführung
class Fehler extends Exception {
private String grund;
private static Fehler instanz = null;;
private Fehler() {}
}
public String getGrund() {
return grund;
}
private void setGrund(String s) {
this.grund = s;
}
public static Fehler getInstanz(String s) {
if (instanz == null) instanz = new Fehler();
instanz.setGrund(s);
return instanz;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-342
Singleton-Pattern
Durchführung
import javax.swing.*;
public class Anwendung {
public static void main(String[] args) {
try {
String input = JOptionPane.showInputDialog
("Bitte die Zahl:");
int n = Integer.parseInt(input);
if (n < 0)
throw Fehler.getInstanz("Negative Zahl " + n);
System.out.println(n);
System.exit(0);
}
catch (Fehler f) {
System.out.println(f.getGrund());main(args);
}}}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-343
Zusammenfassung
Zusammenfassung
Mit throw werden Ausnahmen geworfen.
Eine nicht aufgefangene Ausnahme fürt zum
Programmabbruch.
Ausnahmen sind Objekte von Unterklassen von Exception.
Es gibt vordefinierte Ausnahmen und selbstdefinierte.
Mit catch werden Ausnahmen abgefangen.
Ein finally Block wird immer ausgeführt, auch wenn der
vorangegangene try-Block eine Ausnahme wirft.
Das Singleton Entwurfsmuster besteht darin, von einer Klasse
nur einmal eine Instanz zu erzeugen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Ausnahmebehandlung
9-344
Einführung in die Programmierung
mit Java
Teil 10: Grafische Benutzerschnittstellen
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
15. Dezember 2015
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-345
Inhalt Teil 10: Grafische
Benutzerschnittstellen
33
Motivation
34
Grundaufbau einer Javafx Anwendung
35
Szenegraphen
36
Layout
37
Aktionen und Ereignisse
38
Innere Klassen
39
Lambda Ausdrücke
40
Das Entwurfsmuster Observer
41
Verwendung von FXML
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-346
Motivation
Eine grafische Benutzerschnittstelle (GUI) gestattet es,
Eingaben durch Schaltknöpfe, Menüs, Schiebeschalter etc.
mausgesteuert zu tätigen und Ausgaben in Fenstern zu
präsentieren.
Java stellt große Bibliotheken zur Gestaltung grafischer
Benutzeroberflächen (GUIs) zur Verfügung. Name der
Bibliothek: JavaFX.
Javafx und alle anderen solchen Bibliotheken bauen sehr stark
auf Vererbung auf.
Eine Alternative zu Javafx, die auch standardmäßig zu Java
gehört, heißt Swing.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-347
Anwendungsbeispiel: Bankkonten
Wir möchten ein klickbares Fenster der folgenden Art.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-348
Grundaufbau
Javafx Anwendungen
Will man in einer Anwendung Javafx verwenden, dann muss eine
Javafx Anwendung geschrieben werden; man kann nicht Javafx von
einer normalen Anwendung aus benutzen.
Javafx Anwendungen haben immer folgende Struktur:
import javafx.application.Application;
import javafx.stage.Stage;
public class BankkontoGUI extends Application {
public void start(Stage primaryStage) {
/* hier stehen alle Befehle */
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-349
Grundaufbau
Die Szene
Man kann nicht direkt ins Hauptfenster schreiben, sondern muss
eine “Szene” definieren, welche den Inhalt des Hauptfensters
ausschließlich seines Titels repräsentiert.
...
import javafx.scene.Scene;
import javafx.scene.control.TextField;
...
public void start(Stage primaryStage) {
TextField betragFeld = new TextField("betrag");
Scene scene = new Scene(betragFeld);
primaryStage.setScene(betragFeld);
primaryStage.show();
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-350
Szenegraphen
Szenegraph
Die Szene enthält nur ein anzeigbares Objekt, welches per
Konstruktor übergeben wird.
Um mehrere Objekte in die Szene zu stellen, verwendet man
“Glasscheiben”, Pane, welche mehrere Objekte, die selber
wieder Glasscheiben sein können, enthalten.
So entsteht ein Szenengraph, bzw. ein Szenenbaum, dessen
Knoten, d.h. Verzweigungen, Glasscheiben sind und an dessen
Blättern sich die eigentlichen grafischen Objekte befinden.
Neben Textfeldern sind diese geometrische Objekte (Linien,
Rechtecke, Ellipsen, etc), Knöpfe, Auswahlmenüs,
Fortschrittsbalken, etc.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-351
Szenegraphen
Verwendung der Glasscheiben (Panes)
...
import javafx.scene.layout.Pane;
...
TextField betragFeld = new TextField("Betrag");
TextField kontostandFeld = new TextField("Kontostand");
Pane scheibe = new Pane();
scheibe.getChildren().addAll(betragFeld,kontostandFeld);
Scene scene = new Scene(scheibe);
primaryStage.setScene(scene);
Beachte die Indirektion über getChildren() beim Einstellen
der Textfelder in die Glasscheibe.
Leider liegen jetzt alle Textfelder übereinander und man sieht
nur das zuletzt eingehängte kontostandFeld.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-352
Szenegraphen
Manuelles Positionieren
Mit der relocate() Methode können einmal eingehängte Objekte
nachträglich verschoben werden.
...
kontostandFeld.relocate(0,50);
...
Durch addAll wurde das Textfeld bei der Glasscheibe
“angemeldet”. Nachträgliches Verschieben wird an die Scheibe und
von dort an Szene und Stage weitergemeldet. Mehr zu diesem
“Weitermelden” später.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-353
Layout
Layouts
Das manuelle Positionieren ist zum einen mühsam und
verträgt sich zum anderen schlecht mit Fenstern variabler
Größe und unterschiedlichen Endgeräten.
Günstiger ist die Verwendung von Panes mit Layout-Funktion.
Es gibt u.a.
FlowPane, Fluss-Layout. Komponenten werden nacheinander
eingesetzt.
GridPane, Gitter-Layout. Komponenten werden in ein
tabellenartiges Gitter eingesetzt. Die Höhe und Breite der
Zellen richtet sich nach den Inhalten.
BorderPane, Rand-Layout. Kann bis zu fünf Komponenten an
den Positionen N,S,O,W und Mitte enthalten.
Es gibt noch weitere solche Layout-behafteten Panes. Die
einfache Pane ist nützlich für die Platzierung komplizierter
geometrischer Objekte, etwa eines Diagramms.
Natürlich können die einzelnen Komponenten einer solchen
Pane selbst wieder Panes, ggf. mit Layout, sein.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-354
Layout
Szenegraph unseres Fensters, I
Die “aktiven” Komponenten, hier Textfelder und Knöpfe, führen wir
als Instanzvariablen:
import javafx.scene.control.Button;
...
private TextField kontostandFeld;
private Button einzahlKnopf;
private Button abhebeKnopf;
private TextField betragFeld;
...
public void start(Stage primaryStage) {
...
Man beachte die Verwendung von Knöpfen (Buttons).
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-355
Layout
Szenegraph unseres Fensters, II
Die anderen können lokale Variablen der start-Methode sein, bzw.
bei größeren Anwendungen in Hilfsmethoden geführt werden.
import javafx.scene.control.Label;
...
public void start(Stage primaryStage) {
FlowPane kontostandScheibe = new FlowPane();
Label kontostandEtikett = new Label("Kontostand: ");
kontostandScheibe.getChildren().
addAll(kontostandEtikett,kontostandFeld);
FlowPane betragScheibe = new FlowPane();
Label betragEtikett = new Label("Betrag: ");
betragScheibe.getChildren().
addAll(betragEtikett,betragFeld);
Wir verwenden Etiketten (Labels) um die Textfelder zu beschriften
und fassen Textfeld mit entsprechendem Etikett in einer FlowPane
zusammen. Ebenso kommen die beiden Knöpfe in eine gemeinsame
FlowPane.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-356
Layout
Szenegraph unseres Fensters, III
Schließlich werden alle drei FlowPanes untereinander in eine
GridPane gestellt. Der setVgap Befehl erhöht den Zeilenabstand.
GridPane fensterInhalt = new GridPane();
fensterInhalt.setVgap(10);
fensterInhalt.add(kontostandTafel,0,0);
fensterInhalt.add(betragTafel,0,1);
fensterInhalt.add(knopfTafel,0,2);
Scene scene = new Scene(fensterInhalt);
primaryStage.setScene(scene);
primaryStage.show();
Die GridPane bildet dann den Inhalt unserer Szene und damit des
Fensters.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-357
Aktionen & Ereignisse
Aktionen und Ereignisse
Nun müssen wir unser GUI mit Leben füllen.
Den Inhalt der Textfelder kann man mit getText und setText
auslesen und verändern.
Wie reagieren wir auf das Drücken der Knöpfe?
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-358
Aktionen & Ereignisse
Früher. . .
. . . gab es zu jedem Knopf eine Methode (oder Funktion) mit
Boole’schem Rückgabewert.
War gerade ein Knopf gedrückt, so war der Rückgabewert true;
ansonsten false.
Im Hauptprogramm musste man dann ständig diese Methode
aufrufen um ja keinen Knopfdruck zu verpassen.
Diese Methode, bezeichnet als polling, gilt inzwischen als überholt.
Stattdessen benutzt man ereignisgesteuerte
Eingabenbehandlung (event-driven):
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-359
Aktionen & Ereignisse
Aktionen und Ereignisse
Mit der Methode setOnAction kann man an einen Knopf einen
Behandler (action handler) anheften.
Das ist ein Objekt, welches die Schnittstelle (Interface)
EventHandler<ActionEvent>
implementiert. Diese Schnittstelle enthält nur eine einzige Methode:
public void handle(ActionEvent e)
Der action handler muss also so eine Methode implementieren.
Ist der action handler an einen Knopf geheftet, so wird diese
Methode immer dann aufgerufen, wenn der Knopf gedrückt wird.
Und zwar mit einem Parameter der Klasse ActionEvent, aus dem
man im Rumpf von handle z.B. den Knopf, der gedrückt wurde,
ablesen kann (die “Quelle des Ereignisses”).
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-360
Aktionen & Ereignisse
Beispiel
public class Behandler implements EventHandler<ActionEvent> {
public void handle(ActionEvent ereignis) {
System.out.println("Es wurde " + ereignis.getSource()
+ " gedrueckt.");
}
}
und in start():
EventHandler<ActionEvent> behandler = new Behandler();
einzahlKnopf.setOnAction(behandler);
abhebeKnopf.setOnAction(behandler);
Ausgabe:
Es wurde Button@527ba09f[styleClass=button]'einzahlen' gedrueckt
Es wurde Button@7b3313b8[styleClass=button]'abheben' gedrueckt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-361
Aktionen & Ereignisse
Sinvollere Behandler
Für sinnvollere Aktionen brauchen wir
Eine Instanzvariable konto der Klasse Bankkonto
Innerhalb von handle Zugriff auf konto, sowie auf die
Textfelder und Knöpfe.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-362
Aktionen & Ereignisse
Übergabe der GUI-Komponenten per
Konstruktor
Wir können alle diese Komponenten auch als Instanzvariablen des
Behandlers führen und über den Konstruktor übergeben.
Z.B.: in Behandler.java
private Bankkonto konto;
...
public Behandler(Bankkonto konto, Button abhebeKnopf, ...) {
this.konto = konto;
...
}
Vorteil Anschaulich und im Einklang mit bisherigem Stoff
Nachteil Sehr umständlich, wenn viele Komponenten übergeben
werden müssen. Schon in diesem Beispiel sind es ja fünf! (Konto,
zwei Textfelder, zwei Knöpfe)
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-363
Innere Klassen
Innere Klassen
Alternativ hat man die Möglichkeit, die Definition der Klasse
Behandler innerhalb von BankkontoGUI {...} vorzunehmen.
Hierbei verwendet man ein neues Java Sprachkonstrukt: Innere
Klassen
Eine innere Klasse hat auch Zugriff auf die privaten
Instanzvariablen ihrer umgebenden Klasse und verhält sich
ansonsten wie eine normale Klasse.
Im Computer realisiert werden innere Klassen wie Möglichkeit 1,
also explizite Übergabe der Instanzvariablen bei Konstruktion.
Vorteil Wird oft benutzt, steht so im Buch.
Nachteil Theorie etwas unklar (Sichtbarkeitsbereiche, etc.),
Aufblähung der Sprache.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-364
Innere Klassen
Beispiel
public class BankkontoGUI extends Application {
private Bankkonto konto;
...
public void start(Stage primaryStage) {
this.konto = konto;
...
EventHandler behandler = new Behandler();
einzahlKnopf.setOnAction(behandler);
abhebeKnopf.setOnAction(behandler);
kontostandFeld.setText("" + konto.getKontostand());
...
}
class Behandler implements EventHandler<ActionEvent> {
public void handle(ActionEvent ereignis) {
/*Fortsetzung folgt*/
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-365
Innere Klassen
Beispiel
Object gedrueckt = ereignis.getSource();
double betrag = Double.parseDouble(betragFeld.getText());
if (gedrueckt == einzahlKnopf)
konto.einzahlen(betrag);kontostandFeld.setText(""
+ konto.getKontostand());
else if (gedrueckt == abhebeKnopf)
konto.abheben(betrag);kontostandFeld.setText(""
+ konto.getKontostand());
else ;
} /* Ende der Methode handle */
} /* Ende der inneren Klasse Behandler */
} /* Ende der Klasse BankkontoGUI */
Achtung: == ist hier die physikalische Gleichheit von
Objekt(referenz)en.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-366
Innere Klassen
Anonyme innere Klassen
Eine weitere Möglichkeit besteht darin, eine innere Klasse ohne
eigenen Namen an Ort und Stelle zu definieren.
Dafür muss nur der Name eines implementierten Interfaces
vorhanden sein. Man schreibt also z.B. in start:
EventHandler<ActionEvent> behandler =
new EventHandler<ActionEvent>(){
public void handle(ActionEvent ereignis) {
Object gedrueckt = ereignis.getSource();
...
}};
einzahlKnopf.setOnAction(behandler);
...
Allgemein erzeugt also die Syntax
new Interface(){code};
ein neues Objekt einer namenlosen Klasse, die die Schnittstelle
Interface implementiert.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-367
Innere Klassen
Jedem Knopf seinen eigenen Behandler
Nachdem jetzt der Aufwand für die Bereitstellung der
Behandler-Klasse weitgehend entfällt, kann man auch jedem Knopf
seine eigenes anonymes Behandler-Objekt geben:
einzahlKnopf.setOnAction(new EventHandler<ActionEvent>(){
public void handle(ActionEvent ereignis){
konto.einzahlen(Double.parseDouble(betragFeld.getText()));
kontostandFeld.setText("" + konto.getKontostand());
}});
abhebeKnopf.setOnAction(new EventHandler<ActionEvent>(){
...
});
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-368
Lambda Ausdrücke
Lambda-Ausdrücke
Im besonderen Falle, in dem das zu implementierende Interface
genau eine Methode enthält und die anonyme Klasse keine
Instanzvariablen benötigt, gibt es seit Java 8 eine weitere elegante
Möglichkeit: “Lambdas”.
Beispiel
abhebeKnopf.setOnAction(ereignis -> {
konto.abheben(Double.parseDouble(betragFeld.getText()));
kontostandFeld.setText("" + konto.getKontostand())});
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-369
Lambda Ausdrücke
Lambdas allgemein
Allgemein kann ein Methodenparameter, dessen Typ ein Interface
mit nur einer Methode ist, durch einen Lambda-Ausdruck gegeben
werden.
Dieser hat die Form
(x1 , . . . , xn ) -> body
wobei n die Zahl der formalen Parameter der einzigen Methode des
verlangten Interfaces ist.
Der Rumpf body ist entweder ein Java Ausdruck, der die Parameter
x1 , . . . , xn benutzen darf, oder ein in {}-Klammern stehender
Block, ggf. mit return Statement.
Der Lambda-Ausdruck steht dann für ein frisch erzeugtes Objekt zu
dem geforderten Interface, dessen einzige Methode wie im Rumpf
beschrieben implementiert ist.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-370
Lambda Ausdrücke
Beispiel
interface IntTester {public boolean test(int x);}
class IstGerade implements IntTester {
public boolean test(int x) { return x % 2 == 0;}
}
...
public static boolean fueralle(int[] a, IntTester p) {
boolean result = true;
for (int x:a) result &= p.test(x);
return result;
}
int a[] = {2,4,6,8,10,122};
int b[] = {4,25,289}
System.out.println(fueralle(a,new IstGerade()));
System.out.println(fueralle(b,new IstGerade()));
Ausgabe: true und false.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-371
Lambda Ausdrücke
Verwendung von Lambdas im Beispiel
int a[] = {2,4,6,8,10,122};
int b[] = {4,25,289}
System.out.println(fueralle(a,x -> x<1000));
System.out.println(fueralle(b,x -> {
int q = (int)Math.sqrt(x);
return x == q*q;}));
Ausgabe: true und true.
Anstelle eines Lambdas kann man mit doppeltem Doppelpunkt
auch eine Funktion geeigneten Typs angeben, z.B.:
IstGerade obj = new IstGerade();
// dynamische Methode test1 mit Objekt obj aufrufen
System.out.println(fueralle(b, obj::test1));
// statische Methode test2 aus Klasse IstGerade aurufen
System.out.println(fueralle(a, IstGerade::test2));
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-372
Observer-Pattern
Beobachter
Wir haben das Problem, die Kontostandanzeige mit dem
tatsächlichen Kontostand konsistent zu halten.
Jede Änderung des Kontostandes (auch durch andere GUIs, das
andere Programmteile, o.ä.) sollen sofort wiedergegeben werden.
Die Lösung besteht im Entwurfsmuster Beobachter (Observer).
Jedes Bankkonto verwaltet eine Liste von Beobachtern, die sich bei
ihm registriert haben. Ändert sich der Kontostand, so ruft es bei
jedem registrierten Beobachter eine bestimmte Methode auf.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-373
Observer-Pattern
Konkretes Beispiel
Wir verwenden die Schnittstelle
public interface BankkontoBeobachter {
public void anzeigen(Bankkonto b);
}
Die Klasse Bankkonto muss nun so erweitert werden, dass eine
Liste von BankkontoBeobachtern verwaltet wird und die Methode
anzeigen entsprechend aufgerufen wird:
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-374
Observer-Pattern
Konkretes Beispiel
public class Bankkonto {
private double kontostand;
private ArrayList<BankkontoBeobachter> beobachter;
public void benachrichtigen() {
for (BankkontoBeobachter beob : beobachter)
beob.anzeigen(this);
}
public void einzahlen(double betrag) {
kontostand = kontostand + betrag;
benachrichtigen();
}
public void abheben(double betrag) {...}
public void anheften(BankkontoBeobachter beob) {
beobachter.add(beob);
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-375
Observer-Pattern
Konkretes Beispiel
Die Klasse BankkontoGUI implementiert nun
BankkontoBeobachter.
public class BankkontoGUI extends Application
implements BankkontoBeobachter {
...
public void anzeigen(Bankkonto k) {
kontostandFeld.setText("" +
konto.getKontostand());
}
Jetzt brauchen wir uns um die Aktualisierung des Textfeldes nicht
mehr zu kümmeren. Sie erfolgt ganz automatisch.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-376
Observer-Pattern
Observer und Observable
Die Beobachterverwaltung ist in Java auch vorgefertigt in Form
einer Klasse Observable von der beobachtbare Klassen, z.B.
Bankkonto dann erben können . . .
und einer Schnittstelle Observer, die die Beobachter, z.B.
BankkontoGUI implementieren müssen. Diese Schnittstelle enthält
eine Methode
void update(Observable o,
Object arg)
Die Klasse Observable stellt Methoden addObserver (entspricht
unserem anheften) und notifyObservers (entspricht unserem
benachrichtigen) bereit. Zusätzlich muss bei jeder Änderung die
Methode setChanged aufgerufen werden.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-377
Observer-Pattern
Im Beispiel Bankkonto
class Bankkonto extends Observable {
private double kontostand;
...
void einzahlen(double betrag) {
kontostand = kontostand + betrag;
setChanged();
notifyObservers();
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-378
Observer-Pattern
Anmelden von Beobachtern: entweder so:
public class BankkontoGUI extends Application
implements Observer {
public void update(Observable o, Object arg) {
kontostandFeld.setText("" +
konto.getKontostand());
}
...
public void start(Stage primaryStage) {
konto = new Bankkonto();
konto.addObserver(this);
...
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-379
Observer-Pattern
Oder unter Verwendung von Lambdas
In start:
konto.addObserver((o,arg) ->
kontostandFeld.setText("" + konto.getKontostand()));
Ebenso können wir weitere Beobachter hinzufügen:
Slider ktoAnzeige = new Slider();
...
konto.addObserver((o,arg)->
ktoAnzeige.setValue((int)konto.getKontostand()));
Wie gewohnt erzeugt der Lambda Ausdruck automatisch ein Objekt
der verlangten Schnittstelle, ohne dass eine entsprechende Klasse
eingeführt oder verwendet werden müsste.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-380
Observer-Pattern
Model-View-Controller
Oft kann eine Anwendung mit GUI in die folgenden drei Teile
zerlegt werden:
Ein “Modell” (model): die Daten, mit denen über die GUI
interagiert wird, bzw. die darzustellen sind. Beispiele: das
Bankkonto, der aktuelle Spielzustand, eine Simulation.
Eine “Ansicht” (view): die konkrete grafische Darstellung des
Modells und der zugehörigen Bedienelemente. Beispiele: das
Bankkonto-Fenster mit den Knöpfen, die Darstellung eines
Spiels, die verschiedenen Bildschirme bei einer Android-App,
Die “Steuerung” (controller): die Verwaltung der einzelnen
Teile der Ansicht. In unserem Beispiel die Listener und
Observer-Methoden, ausserdem, die Umschaltung zwischen
Bildschirmen, etc.
Es wird empfohlen, diese drei Teile voneinander möglichst
getrennt zu halten, z.B. nicht:
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-381
Observer-Pattern
Typische Verletzungen von
“Model-View-Controller”
In der Klasse Bankkonto und ihren Methoden direkt in
Textfelder schreiben
Den Kontostand ausschliesslich in Form des Inhalts des
Textfeldes zu speichern
Das Fenster mit über Methode der Klasse Bankkonto zu
erzeugen.
Bei der Verwendung von Frameworks wie JavaFX ist die Trennung
von View und Controller nicht so leicht durchzusetzen und auch
nicht mehr so wichtig, da der grösste Teil der Steuerung vom
Framework bereitgestellt wird.
In unserem Beispiel befinden sich View und Controller auch in
derselben Klasse.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-382
Verwendung von FXML
Motivation für FXML
Die Festlegung des Aussehens der Benutzeroberfläche durch
Java Befehle ist umständlich und unübersichtlich.
Als Abhilfe wurde (und das ist auch bei anderen Bibliotheken
und Frameworks so) ein XML Standard eingeführt, hier FXML,
mit dem GUI Layouts formal beschrieben werden können.
Mit speziellen Ladefunktionen können dann solche XML
Spezifikationen einer GUI eingelesen werden und die
entsprechenden Objekte automatisch erzeugt werden.
Solche XML Spezifikationen können dann auch interaktiv mit
einem entsprechenden grafischen Editor, hier z.B. dem
“SceneBuilder” erzeugt werden.
Die grafische Ansicht der GUI befindet sich dann an separater
Stelle, vgl. Model-View-Controller Prinzip.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-383
Verwendung von FXML
FXML Definition für unser Beispiel
Im Beispiel könnte die FXML in einer Datei BankkontoGUI.fxml
des folgenden Inhalts stehen:
<?xml
version="1.0" encoding="UTF-8"?>
<?import
<?import
<?import
<?import
<?import
<?import
javafx.scene.paint.*?>
javafx.scene.effect.*?>
javafx.scene.text.*?>
javafx.scene.control.*?>
java.lang.*?>
javafx.scene.layout.*?>
<GridPane xmlns="http://javafx.com/javafx/8.0.66"
xmlns:fx="http://javafx.com/fxml/1">
<children>
<FlowPane>
<children>
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-384
Verwendung von FXML
FXML-Datei Fortsetzung
<Label text="Kontostand: "/>
<TextField fx:id="kontostandFeld" editable="false"/>
</children>
</FlowPane>
<FlowPane GridPane.rowIndex="1">
<children>
<Label text="Betrag: " />
<TextField fx:id="betragFeld" text="0.0"/>
</children>
</FlowPane>
<FlowPane GridPane.rowIndex="2">
<children>
<Button fx:id="einzahlKnopf" text="einzahlen"/>
<Button fx:id="abhebeKnopf" text="abheben"/>
</children>
</FlowPane>
</children>
</GridPane>
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-385
Verwendung von FXML
Erklärung der XML-Datei
Die FXML Datei ist ähnlich einer HTML-Datei strukturiert.
Die einzelnen Komponenten können entweder einzelne Tags
mit Attributen sein, wie das Label oder das TextField
oder geschachtelte Komponenten mit Unterkomponenten wie
GridPane oder FlowPane sein
Das xmlns Attribut zu Beginn definiert den entsprechenden
XML-Namensraum, in ihm sind die verwendeten Komponenten
festgelegt
Die import Direktiven werden vom FXML-Lader benötigt,
siehe folgende Folien
Man kann die FXML-Datei auch interaktiv mit dem
SceneBuilder erzeugen und in der Regel wird das auch so
gemacht. Siehe Demo.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-386
Verwendung von FXML
Verwendung der FXML Spezifikation
Man kann jetzt in start die FXML-Datei wie folgt laden:
import
import
import
...
import
javafx.fxml.FXMLLoader;
java.net.URL;
javafx.fxml.FXML;
javafx.collections.ObservableMap;
FXMLLoader fxmlloader = new FXMLLoader(new URL(
"file:///home/mhofmann/EIP15/Vorlesung/BankkontoGUI.fxml"));
GridPane fensterInhalt=(GridPane)fxmlloader.load();
ObservableMap<String,Object> namespace = fxmlloader.getNamespa
einzahlKnopf = (Button)namespace.get("einzahlKnopf");
abhebeKnopf = (Button)namespace.get("abhebeKnopf");
kontostandFeld = (TextField)namespace.get("kontostandFeld");
betragFeld = (TextField)namespace.get("betragFeld");
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-387
Verwendung von FXML
Erklärung
Der Konstruktor FXMLLoader erzeugt einen neuen
FXML-Lader für unsere FXML-Datei.
Die load-Methode verarbeitet dann die FXML Datei und
liefert das entsprechende Wurzelobjekt, hier die GridPane
zurück.
Mit getNamespace erhält man die Gesamtheit der erzeugten
aktiven Objekte über ihren fx-Bezeichner (der war als
entsprechendes Attribut in der FXML-Datei dazugegeben)
Die aktiven Objekte werden wie gewohnt vorher als private
Instanzvariablen deklariert.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-388
Verwendung von FXML
Laden mit Reflexion
Man kann die Komponenten auch so laden:
FXMLLoader fxmlloader = new FXMLLoader(new URL(
"file:///home/mhofmann/EIP15/Vorlesung/BankkontoGUI.fxml"));
fxmlloader.setController(this);
GridPane fensterInhalt=(GridPane)fxmlloader.load();
Die oben deklarierten privaten Instanzvariablen werden dann mit
den entsprechenden erzeugten Objekten automatisch verbunden.
Dies verwendet die sogenannte Reflexion, ein zusätzliches
Java-Feature, von dem bisher nicht die Rede war. Reflexion erlaubt
es, auf die syntaktische Struktur von Objekten zur Laufzeit
zuzugreifen.
Vorsicht: die Typprüfung wird durch Reflexion weitgehend außer
Kraft gesetzt; Typfehler zeigen sich oft erst zur Laufzeit.
Für das Verständnis von Frameworks kann eine oberflächliche
Kenntnis des Konzeptes nützlich sein.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-389
Verwendung von FXML
Beispiel für Reflexion
import java.lang.reflect.Field;
...
public static void set50(Object x,String vname) throws Exception
Field v = x.getClass().getDeclaredField(vname);
v.setAccessible(true);
v.set(x,50);
}
Diese Methode set50 kann ein beliebiges int-Feld eines beliebigen
Objektes auf 50 setzen. Das Objekt und der Name des Feldes (als
String!) werden übergeben.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-390
Verwendung von FXML
Weiterführende Themen zum Selbststudium
Verändern des Aussehens der GUI-Komponenten durch
Style-Sheets (CSS)
Weitere GUI-Komponenten: Accordeon, Pulldown Menu,
Charts, . . .
DoubleProperty, BooleanProperty, etc.: Observer-Pattern für
elementare Datentypen
Properties und Bindings: Verallgemeinerung des
Observer-Patterns
Geometrische Objekte in Panes zeichnen
Affine Transformationen (Drehen, Verschieben, etc) auf
GUI-Komponenten anwenden
3D Grafik: Kameras, Lampen
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-391
Verwendung von FXML
Zusammenfassung GUI
Die Bibliothek JavaFX stellt GUI Komponenten bereit.
Mit Ereignissen (Event) und Zuhörern (Listener) wird auf
Benutzereingaben reagiert. Letztendlich wird bei
Benutzereingaben eine vom Programmierer für diesen Zweck
bereitgestellte Methode aufgerufen.
Innere Klassen erlauben die komfortable Implementierung der
erforderlichen Behandler-Klassen.
Lambda-Ausdrücke ermöglichen das Übergeben von Code an
andere Methoden mit reduziertem notationellen Umstand.
Die GUI kann durch Verwendung des Beobachter-Musters mit
den dargestellten Daten konsistent gehalten werden.
Model-View-Controller: Empfehlung, das zugrundeliegende
Modell von der grafischen Darstellung und Bedienoberfläche
getrennt zu halten
FXML ermöglicht die kompakte Definition des Layouts von
GUI Komponenten.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Grafische Benutzerschnittstellen
10-392
Einführung in die Programmierung
mit Java
Teil 11: Nebenläufigkeit
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
22. Dezember 2015
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-393
Inhalt Teil 11: Nebenläufigkeit
42
Grundlagen
43
Threadunterbrechung
44
Synchronisation
Race condition
Deadlock
45
Zusammenfassung
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-394
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Nebenläufigkeit vs. Paralleles Rechnen
• Paralleles Rechnen: Ziel ist schnellere Ausführung
deterministischer Programme durch gleichzeitige Verwendung
mehrerer Prozessoren.
Computer mit mehreren Kernen und Cloud-Architekturen sind
inzwischen Standard und ermöglichen paralleles Rechnen.
• Dagegen bedeutet Nebenläufigkeit (engl. Concurrency)
nicht-deterministische Berechnungen durch zufällig abwechselnd
ausgeführte interagierende Prozesse.
Beispiel Reaktion auf verschiedene externe Ereignisse:
UniworX Webserver verarbeitet scheinbar gleichzeitig viele
Benutzeranfragen.
Dies muss nicht unbedingt parallel erfolgen: auf einem einzelnen
Prozessorkern wird einfach ständig abgewechselt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-395
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Threads
Ein Programm zusammen mit dem Speicherbereichen, in denen es
abläuft, bezeichnet man als Prozess.
Ein Programm kann auch
aus mehreren interagierenden Prozessen bestehen.
Ein Thread (“Faden”) ist ein Prozess, der einen eigenen Keller
(stack, Speicher für lokale Variablen), aber keine eigene Halde
(heap, Speicher für Objekte), hat.
Gleichzeitig ablaufende Threads eines nebenläufigen Programms
können über die gemeinsame Halde interagieren:
Laufen mehrere Threads gleichzeitig ab, so haben sie also
Zugriff auf dieselben Objekte, aber jeder Thread hat seine
eigenen lokalen Variablen.
Der gemeinsame Zugriff auf die Objekte in der Halde erlaubt
die Kommunikation zwischen den Threads.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-396
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Scheinbare Gleichzeitigkeit
Üblicherweise gibt es mehr Threads als Prozessorkerne.
Das Betriebssystem kümmert sich darum, alle Threads auf alle
verfügbaren Prozessorkerne zu verteilen.
Dabei gibt es verschiedene Strategien. Meist werden alle Threads
regelmäßig unterbrochen, um alle Threads scheinbar gleichmäßig
auszuführen.
Je nach dem Verhältnis zwischen Threads und Kernen laufen
mehrere Threads in Wahrheit nicht gleichzeitig, sondern der Reihe
nach, jeweils für eine kurze Zeitscheibe (time slice). Die
Auswirkungen dieses Unterschiedes sind für uns aber meistens
unerheblich.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-397
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Anwendungen von Threads
Viele professionelle Anwendungen verwenden Threads:
oder recht ähnliche Konzepte
Datenbanken
Zugriffe laufen in eigenen Threads ab.
Webserver
Anfragen werden jeweils in eigenen Threads verarbeitet.
Fensterorientierte Benutzeroberflächen
Ein Thread für die Fenster, ein weiterer Thread für die
eigentliche Anwendung.
...
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-398
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Thread und Runnable
Zur nebenläufigen Ausführung erzeugen wir für jeden Thread ein
Objekt der Klasse Thread.
Im Konstruktor Thread(Runnable r) übergeben wir ein Objekt,
welches das Interface Runnable implementiert:
public interface Runnable {
public abstract void run();
}
Nach dem Erstellen des neuen Thread-Objekts müssen wir den
Thread noch mit der Methode void start() starten:
Runnable mytask = ...
Thread thread = new Thread(mytask);
thread.start();
// more code
Ausführung wird mit den Befehlen nach // more code fortgesetzt.
Gleichzeitig wird in einem neuen Thread jedoch auch der Code der
run-Methode des Objekts mytask ausgeführt!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-399
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Unsere ersten Threads
Variante A
import java.util.Date;
public class Greeter implements Runnable{
final static int DELAY = 1000;
private String botschaft;
public Greeter(String botschaft) {
this.botschaft = botschaft;
}
@Override
public void run() {
try {
while (true) {
Date jetzt = new Date();
System.out.println(jetzt + " " + botschaft);
Thread.sleep(DELAY);
}
} catch (InterruptedException e) {}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-400
Nebenläufigkeit
}
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
public static void main(String[] args) {
Greeter g1 = new Greeter("Guten Tag.");
Thread th1 = new Thread(g1);
Thread th2 = new Thread(new Greeter("Auf Wiedersehen."));
th1.start();
th2.start();
System.out.println("Grüßer gestartet!");
}
Ausgabe
Grüßer gestartet!
Mon Dec 21 17:41:04
Mon Dec 21 17:41:04
Mon Dec 21 17:41:05
Mon Dec 21 17:41:05
Mon Dec 21 17:41:06
Mon Dec 21 17:41:06
Mon Dec 21 17:41:07
Mon Dec 21 17:41:07
Martin Hofmann, Steffen Jost
CET
CET
CET
CET
CET
CET
CET
CET
2015
2015
2015
2015
2015
2015
2015
2015
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Einführung in die Programmierung
Nebenläufigkeit
11-401
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Erklärung Greeter
Der Effekt von start ist, den Thread mit eigenem Keller
(Stack) zu starten. Der neue Thread startet mit dem Aufruf
der run Methode.
Der ursprüngliche, startende Thread läuft auch weiter!
Die Methode sleep versetzt einen Thread für eine gegebene
Zahl von Millisekunden in Schlaf.
Dabei kann eine InterruptedException auftreten, welche
wir hier einfach mal mit einem leeren Handler ignorieren —
das sollte man nicht machen, und wir kommen deshalb im
nächsten Abschnitt auch gleich darauf zurück!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-402
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Unser erster Thread
Variante B
Alternativ kann man auch von Thread erben, da diese selbst
Runnable implementiert:
import java.util.Date;
public class Gruesser extends Thread {
final static int DELAY = 1000;
private String botschaft;
public Gruesser(String s) {
botschaft = s;
}
public void run() {
try {
while (true) {
Date jetzt = new Date();
System.out.println(jetzt + " " + botschaft);
sleep(DELAY);
// sleep wurde geerbt.
}
} catch (InterruptedException e) {}}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-403
Nebenläufigkeit
}
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
public static void main(String[] args) {
Gruesser th1 = new Gruesser("Guten Tag.");
Gruesser th2 = new Gruesser("Auf Wiedersehen.");
th1.start();
th2.start();
System.out.println("Grüßer gestartet!");
}
Zum Starten braucht man nur ein Objekt zu erzeugen, da Gruesser
durch die Vererbung bereits selbst zu einem Thread geworden ist.
Vorsicht:
Ein neuer Thread wird nur mit start gestartet, also niemals
einfach nur die run-Methode selbst aufrufen!
Der Effekt wäre nur die Abarbeitung der Befehle im Rumpf von run
ohne Starten eines neuen Threads, also ohne Nebenläufigkeit.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-404
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Effekt
Grüßer gestartet!
Mon Dec 21 17:41:04
Mon Dec 21 17:41:04
Mon Dec 21 17:41:05
Mon Dec 21 17:41:05
Mon Dec 21 17:41:06
Mon Dec 21 17:41:06
Mon Dec 21 17:41:07
Mon Dec 21 17:41:07
Mon Dec 21 17:41:08
Mon Dec 21 17:41:08
Mon Dec 21 17:41:09
Mon Dec 21 17:41:09
Mon Dec 21 17:41:10
Mon Dec 21 17:41:10
Mon Dec 21 17:41:11
Mon Dec 21 17:41:11
Mon Dec 21 17:41:12
Mon Dec 21 17:41:12
Martin Hofmann, Steffen Jost
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Auf Wiedersehen.
Guten Tag.
Einführung in die Programmierung
Nebenläufigkeit
11-405
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Nicht nur run aufrufen!
public static void main(String[] args) {
Gruesser th1 = new Gruesser("Guten Tag.");
Gruesser th2 = new Gruesser("Auf Wiedersehen.");
th1.run();
th2.run();
System.out.println("Grüßer gestartet!");
}
Effekt bei Ausgabe
Mon
Mon
Mon
Mon
Mon
Mon
Mon
Dec
Dec
Dec
Dec
Dec
Dec
Dec
21
21
21
21
21
21
21
17:44:42
17:44:43
17:44:44
17:44:45
17:44:46
17:44:47
17:44:48
CET
CET
CET
CET
CET
CET
CET
2015
2015
2015
2015
2015
2015
2015
Guten
Guten
Guten
Guten
Guten
Guten
Guten
Tag.
Tag.
Tag.
Tag.
Tag.
Tag.
Tag.
Es kommt gar nicht zur Ausführung des zweiten Threads!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-406
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Thread-Erstellung
Ein eigener Thread wird wahlweise definiert duch. . .
Erzeugung eines normalen Thread-Objekts mit Übergabe eines
Objekts des Interface Runnable im Konstruktor; oder durch
2 Erben von Thread.
1
In beiden Fällen soll man Methode void run() überschreiben.
Ein Thread mit eigenem Keller (Stack) wird dann durch Aufruf
von void start() gestartet.
Der neue Thread arbeitet dann die Methode void run() ab.
Dies geschieht nebenläufig zu dem ursprünglichen Thread, in
dem start aufgerufen wurde.
Wenn man ohnehin nur run implementiert und sonst keine
Methoden von Thread überschreibt, dann empfiehlt es sich,
lediglich das Interface Runnable zu implementieren.
Die dynamischen Methoden von Thread kann man trotzdem noch
über die statische Methode Thread.currentThread() aufrufen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-407
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Threads unterbrechen
Ruft man bei einem Thread die Methode interrupt() auf, so
kann dessen Aufmerksamkeit erlangt werden:
Die Methode isInterrupted() liefert dann true zurück;
Befindet sich der Thread “im Schlaf” (aufgrund von sleep), so
wird die Ausnahme InterruptedException geworfen.
Man kann dies dazu nutzen, den Thread auf Wunsch von außen
vernünftig zu beenden.
Beispiel:
Mehrere Threads suchen in unterschiedlichen Datenbanken.
Hat einer das Gesuchte gefunden, so kann er die anderen
unterbrechen und auffordern, die Datenbank ordentlich zu verlassen
und zu terminieren.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-408
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Beispiel für Unterbrechung
In der Klasse Gruesser schreiben wir:
public void run() {
try {
while (true) {
if (isInterrupted())
throw new InterruptedException();
Date jetzt = new Date();
System.out.println(jetzt + " " + botschaft);
sleep(DELAY); //InterruptedException hier möglich
}
} catch (InterruptedException e) {
System.out.println("Fertig (" + botschaft + ")");
}
}
Ein von aussen ausgelöster Interrupt setzt lediglich isInterrupted
auf true; es sei denn, der Thread schläft gerade, dann wird eine
Exception geworfen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-409
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Außerdem:
class WatchDog extends Thread{
private Thread t;
WatchDog(Thread t) { this.t = t; }
public void run() {
try {
sleep(10000);
t.interrupt();
}
catch (InterruptedException e) {}
}}
In der main-Methode:
..
.
WatchDog w = new WatchDog(th2);
th1.start();
th2.start();
w.start();
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-410
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Ausgabe mit WatchDog
Mon Dec 21 18:03:50 CET 2015
Mon Dec 21 18:03:51 CET 2015
Mon Dec 21 18:03:51 CET 2015
Mon Dec 21 18:03:52 CET 2015
Mon Dec 21 18:03:52 CET 2015
Mon Dec 21 18:03:53 CET 2015
Mon Dec 21 18:03:53 CET 2015
Fertig (Auf Wiedersehen.)
Mon Dec 21 18:03:54 CET 2015
Mon Dec 21 18:03:55 CET 2015
Mon Dec 21 18:03:56 CET 2015
Mon Dec 21 18:03:57 CET 2015
Martin Hofmann, Steffen Jost
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Guten
Guten
Guten
Guten
Tag.
Tag.
Tag.
Tag.
Einführung in die Programmierung
Nebenläufigkeit
11-411
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Effekt
Grüßer gestartet!
Mon Dec 21 17:41:04
Mon Dec 21 17:41:04
Mon Dec 21 17:41:05
Mon Dec 21 17:41:05
Mon Dec 21 17:41:06
Mon Dec 21 17:41:06
Mon Dec 21 17:41:07
Mon Dec 21 17:41:07
Mon Dec 21 17:41:08
Mon Dec 21 17:41:08
Mon Dec 21 17:41:09
Mon Dec 21 17:41:09
Mon Dec 21 17:41:10
Mon Dec 21 17:41:10
Mon Dec 21 17:41:11
Mon Dec 21 17:41:11
Mon Dec 21 17:41:12
Mon Dec 21 17:41:12
Martin Hofmann, Steffen Jost
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Auf Wiedersehen.
Guten Tag.
Einführung in die Programmierung
Nebenläufigkeit
11-412
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Effekt
Grüßer gestartet!
Mon Dec 21 17:41:04
Mon Dec 21 17:41:04
Mon Dec 21 17:41:05
Mon Dec 21 17:41:05
Mon Dec 21 17:41:06
Mon Dec 21 17:41:06
Mon Dec 21 17:41:07
Mon Dec 21 17:41:07
Mon Dec 21 17:41:08
Mon Dec 21 17:41:08
Mon Dec 21 17:41:09
Mon Dec 21 17:41:09
Mon Dec 21 17:41:10
Mon Dec 21 17:41:10
Mon Dec 21 17:41:11
Mon Dec 21 17:41:11
Mon Dec 21 17:41:12
Mon Dec 21 17:41:12
Martin Hofmann, Steffen Jost
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
CET
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
2015
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Guten Tag.
Auf Wiedersehen.
Auf Wiedersehen.
Guten Tag.
Einführung in die Programmierung
Nebenläufigkeit
11-412
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Verzahnung
public class Concurrent implements Runnable {
final static int DELAY = 10;
private String botschaft;
public Concurrent(String s) {
botschaft = s;
}
public void run() {
try {
while (true) {
System.out.print(botschaft);
sleep(DELAY);
} } catch (InterruptedException e) {}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-413
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Verzahnung
public static void main(String[] args) {
new Thread(new Concurrent("A")).start();
new Thread(new Concurrent("B")).start();
new Thread(new Concurrent("C")).start();
new Thread(new Concurrent("D")).start();
}
Ausgabe
ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD
ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDACBDACBDACBD
ACBDACBDACBDACBDACBDACBDADCBADCBADCBADCBADCBADCBADCB
ACDBACDBACDBACDBACDBCDABCDABCDBACBDACBDACDBADBCADBAC
DBACDABCDABCDBACDABCDBACDBACDBACDBACDBACDBACDBACDBAC
Reihenfolge der Abarbeitung der Einzelschritte verschiedener
Threads ist nicht vorhersagbar. Die Möglichkeiten der Verzahnung
der Einzelschritte wird schnell immens, so dass auch das
Gesamtergebnis nicht vorhersagbar/testbar wird.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-414
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Verzahnung
public static void main(String[] args) {
new Thread(new Concurrent("A")).start();
new Thread(new Concurrent("B")).start();
new Thread(new Concurrent("C")).start();
new Thread(new Concurrent("D")).start();
}
Ausgabe
ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD
ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDACBDACBDACBD
ACBDACBDACBDACBDACBDACBDADCBADCBADCBADCBADCBADCBADCB
ACDBACDBACDBACDBACDBCDABCDABCDBACBDACBDACDBADBCADBAC
DBACDABCDABCDBACDABCDBACDBACDBACDBACDBACDBACDBACDBAC
Reihenfolge der Abarbeitung der Einzelschritte verschiedener
Threads ist nicht vorhersagbar. Die Möglichkeiten der Verzahnung
der Einzelschritte wird schnell immens, so dass auch das
Gesamtergebnis nicht vorhersagbar/testbar wird.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-414
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Verzahnung
public static void main(String[] args) {
new Thread(new Concurrent("A")).start();
new Thread(new Concurrent("B")).start();
new Thread(new Concurrent("C")).start();
new Thread(new Concurrent("D")).start();
}
Ausgabe
ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD
ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDACBDACBDACBD
ACBDACBDACBDACBDACBDACBDADCBADCBADCBADCBADCBADCBADCB
ACDBACDBACDBACDBACDBCDABCDABCDBACBDACBDACDBADBCADBAC
DBACDABCDABCDBACDABCDBACDBACDBACDBACDBACDBACDBACDBAC
Reihenfolge der Abarbeitung der Einzelschritte verschiedener
Threads ist nicht vorhersagbar. Die Möglichkeiten der Verzahnung
der Einzelschritte wird schnell immens, so dass auch das
Gesamtergebnis nicht vorhersagbar/testbar wird.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-414
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Synchronisation
Greifen mehrere Threads auf dasselbe Objekt modifizierend zu, so
muss sichergestellt werden, dass sie sich nicht gegenseitig in die
Quere kommen.
Beispiel Wir betrachten wieder folgende Klasse für Bankkonten:
class BankAccount {
private double balance;
BankAccount() {balance = 0;}
public void deposit(double amount) {
System.out.println("Depositing " + amount);
double newBalance = balance + amount;
System.out.println("New balance is: " + newBalance);
balance = newBalance;
}
public void withdraw(double amount) {
System.out.println("Withdrawing " + amount);
double newBalance = balance - amount;
System.out.println("New balance is: "+ newBalance);
balance = newBalance;
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-415
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Synchronisation
Dieser Thread zahlt 10-Mal hintereinander 100e ein:
class DepositThread extends Thread {
private BankAccount account;
private double amount;
final static int DELAY = 100;
DepositThread(
BankAccount account, double amount) {
this.account = account;
this.amount = amount;
}
public void run() {
try {
for (int i = 0; i <= 10; i++) {
if (isInterrupted())
throw new InterruptedException();
account.deposit(amount);
sleep(DELAY);
}
}
catch(InterruptedException e){}
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-416
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Synchronisation
. . . und dieser hebt 10-Mal hintereinander 100e ab:
class WithdrawalThread extends Thread {
private BankAccount account;
private double amount;
final static int DELAY = 100;
WithdrawalThread(BankAccount account, double amount) {
this.account = account;
this.amount = amount;
}
public void run() {
try {
for (int i = 0; i <= 10; i++) {
if (isInterrupted())
throw new InterruptedException();
account.withdraw(amount);
sleep(DELAY);
}
}
catch(InterruptedException e){}
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-417
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Synchronisation
Jetzt lassen wir beide Threads parallel laufen:
public static void main(String[] args) {
BankAccount account = new BankAccount();
DepositThread th1 = new DepositThread(account,100);
WithdrawalThread th2 = new WithdrawalThread(account,100)
th1.start();
th2.start();}
Ein paarmal geht es gut:
Depositing 100.0
New balance is: 100.0
Withdrawing 100.0
New balance is: 0.0
Depositing 100.0
New balance is: 100.0
Withdrawing 100.0
New balance is: 0.0
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-418
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Synchronisationsfehler
. . . aber plötzlich:
New balance is: 0.0
Depositing 100.0
New balance is: 200.0
Depositing 100.0
New balance is: 100.0
Withdrawing 100.0
New balance is: 0.0
Withdrawing 100.0
New balance is: -100.0
Dieser Effekt passiert nicht jedesmal und auch nicht immer in
derselben Weise!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-419
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Erklärung
Die Methoden withdraw und deposit werden von den
nebenläufigen Threads aufgerufen und ausgeführt.
Es kann vorkommen, dass der abhebende Thread an der
kommentierten Stelle unterbrochen wird:
public void withdraw(double amount) {
System.out.println("Withdrawing " + amount);
double newBalance = balance - amount;
// *** UNTERBRECHUNG HIER ***
System.out.println("New balance is: "+ newBalance);
balance = newBalance;
}
Vor der Fortsetzung der Ausführung wird nun der einzahlende
Thread aufgerufen und führt deposit vollständig aus.
Nach der Fortsetzung steht in der lokalen Variable newBalance ein
veralteter Wert; welcher fälschlicherweise zurückgeschrieben wird!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-420
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Race condition
Bei nebenläufiger Programmierung muss man bedenken, dass die
einzelnen Statements nebenläufiger Threads beliebig verzahnt
abgearbeitet werden können. Nur die Reihenfolge von Statements
innerhalb eines Threads ist durch den Kontrollfluss vorgegeben.
Hängt das Programmergebnis von der Art und Weise der
Verzahnung ab, so liegt eine Race Condition vor.
Race Conditions sind gefürchtet, da sie zu schwer
vorhersagbarem und schwer reproduzierbarem
Programmverhalten führen.
Eine Race Condition manifestiert sich oft nur sehr selten (etwa
1 Mal pro 1000 Programmabläufe) und ist daher durch Testen
kaum aufzuspüren!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-421
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Das Schlüsselwort synchronized
Im Beispiel kann man dadurch Abhilfe schaffen, dass man die
Methoden deposit und withdrawal mit dem Schlüsselwort
synchronized kennzeichnet.
public synchronized void deposit( double amount) { ... }
public synchronized void withdraw(double amount) { ... }
Achtung:
Es ist keine Lösung, deposit irgendwie “atomar” zu schreiben:
System.out.println("Depositing " + amount + "\n" +
"New balance is: " + balance+=amount):
Ein zusammengesetztes Statement wie letzteres wird nämlich in
mehrere Bytecode - Befehle übersetzt und die können auch wieder
mit anderen Befehlen verzahnt werden.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-422
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Wie wirkt synchronized?
Jedes Objekt ist mit einem Monitor ausgestattet. Dieser
Monitor beinhaltet eine Boole’sche Variable und eine
Warteschlange für Threads.
Ruft ein Thread eine synchronized Methode auf, so wird
zunächst geprüft, ob die assoziierte Boole’sche Variable des
Objekts true (=”frei”) ist. Falls ja, so wird die Variable auf
false (=”besetzt”) gesetzt. Falls nein, so wird der aufrufende
Thread blockiert und in die Warteschlange eingereiht.
Verlässt ein Thread eine synchronized Methode, so wird
zunächst geprüft, ob sich wartende Threads in der Schlange
befinden. Falls ja, so darf deren erster die von ihm gewünschte
Methode ausführen. Falls nein, so wird die mit dem Objekt
assoziierte Boole’sche Variable auf true (=”frei”) gesetzt.
Vergleich: An jedem Objekt hängt ein Mikrofon. Nur, wer es in
der Hand hält, kann bei dem Objekt synchronisierte Methoden
aufrufen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-423
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Terminologie
Führt ein Thread gerade eine synchronisierte Methode bei
einem Objekt aus, so sagt man, dieser Thread “hält das Lock”
dieses Objekts. (holds the objects’ lock).
Der Monitor stellt sicher, dass zu jedem Zeitpunkt immer nur
ein Thread das Lock eines Objekts halten kann.
Nur eine von i.a. mehreren synchronisierten Methoden eines
Objekts kann also jeweils zu einem gegebenen Zeitpunkt
ausgeführt werden.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-424
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Synchronisierte Methoden
Der häufigste Fall ist die komplette Synchronisation eines
Methodenrumpfes:
public synchronized EinTyp methname(Arg a) {
\\ Rumpf der Methode methname
}
Dies ist jedoch nahezu gleich zu:
Abgesehen von Optimierung
public EinTyp methname(Arg a) {
synchronized(this) {
\\ Rumpf der Methode methname
}
}
Es ist also auch möglich, nur einzelne Programm-Blöcke zu
synchronisieren.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-425
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Blöcke synchronisieren
Synchronisation einzelner Programm-Blöcke:
synchronized(lock) {
// synchronisierter Code
}
Hier ist lock das Objekt, dessen Lock verwendet wird.
Es darf ein beliebiges Objekt verwendet werden.
Ein Thread darf einen synchronisierten Bereich nur dann
betreten, wenn das Lock des Lock-Objekts frei ist —
ansonsten ist der Thread blockiert, bis das Lock frei ist.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-426
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Fallstrick
Auch wenn alle Methoden eines Objektes synchronisiert sind,
können Probleme auftreten, z.B. bei dem Lesen mehrerer Werte:
int x = obj.getSyncAttribute1();
int y = obj.getSyncAttribute2();
Zwischen diesen beiden Zuweisung könnte eine Unterbrechung
stattfinden, in der sich obj ändert — x passt dann nicht mehr zu y!
Abhilfe durch Synchronisation
synchronized(obj) {
int x = obj.getSyncAttribute1();
int y = obj.getSyncAttribute2();
}
Während einer Unterbrechung werden andere Threads blockiert,
welche obj durch synchronisierter Methoden abändern wollen.
Hinweis Die Verwendung unveränderlicher Objekte (immutable)
kann solche Probleme von vornherein ausschließen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-427
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Deadlock
Das Problem der Race Condition ist somit behoben; aber dafür
handeln wir uns gleich das nächste Problem ein: Warten zwei
Threads gegenseitig auf die Freigabe von Locks, so kann keiner der
beiden Threads weiterarbeiten. Es liegt eine Verklemmung (engl.:
Deadlock) vor.
Vergleich: Zur Vermeidung von Verklemmungen darf man bei
Stau nicht in eine Kreuzung einfahren. Täte man es doch, so
könnte bei vier Kreuzungen, die ein Quadrat bilden, eine
Verklemmung entstehen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-428
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Beispiel Verklemmung
public synchronized void deposit(double amount) {
System.out.println("Depositing " + amount);
double newBalance = balance + amount;
System.out.println("New balance is: " + newBalance);
balance = newBalance;
}
public synchronized void withdraw(double amount) {
while (balance < amount)
; /* tue nichts */
System.out.println("Withdrawing " + amount);
double newBalance = balance - amount;
System.out.println("New balance is: " + newBalance);
balance = newBalance;}}
Wird die Programmzeile /* tue nichts */ erreicht, so
verklemmt sich das Gesamtsystem, da ja jeder Thread, der versucht
deposit aufzurufen, sofort blockiert wird.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-429
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Abhilfe: wait und notifyAll
Jedes Objekt (also die Klasse Object)
bietet die Methoden wait und notifyAll an.
Beide Methoden dürfen nur mit Objekten aufgerufen werden,
deren Lock vom aktuellen Thread gehalten wird; d.h.
1
2
this.wait(); in synchronisierten Methoden
synchronized(obj) { ... obj.wait(); ... }
Ansonsten Ausnahme IllegalMonitorStateException
Wird wait aufgerufen, so wird der ausführende Thread in den
Wartezustand versetzt.
Achtung: Das Lock des Objekts wird dadurch wieder frei!
Wird notifyAll aufgerufen, so werden alle Threads, die auf
das Objekt warten, in den blockierten Zustand versetzt und
können so bei nächster Gelegenheit das Lock wieder erhalten.
Dies ist nützlich, wenn man darauf warten möchte, dass ein anderer
Thread ein Objekt günstig abändert.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-430
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Zustandsmodell
Jeder Thread kann einen von fünf Zuständen innehaben:
laufend (running)
— wird momentan ausgeführt
bereit (ready)
— kann laufen, aber kein Kern frei
blockiert (blocked) — sychronisierter Bereich nicht betretbar
schlafend (sleeping) — sleep aufgerufen
wartend (waiting)
— wait aufgerufen
Jedes Objekt beinhaltet
1
eine eingebaute Boole’sche Variable zur Synchronisation,
2
eine Menge von blockierten Threads und
3
eine Menge von wartenden Threads.
Außerdem gibt es eine zentrale Schlange von bereiten Threads.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-431
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Zustandsübergänge I
Nach Ablauf einer Zeitscheibe wird ein laufender Thread
“bereit” gemacht und der erste “bereite” Thread “laufend”
gemacht.
Bei n-Kernen die ersten n “bereiten” Threads
Beim Versuch der Ausführung einer synchronisierten Methode1
auf einem Objekt, dessen Lock nicht frei ist, wird der
aufrufende Thread in den Zustand “blockiert” versetzt und in
die Menge der durch das Objekt blockierten Threads
eingereiht.
Beim Verlassen einer synchronisierten Methode1 wird einer der
blockierten Threads in der zugehörigen Menge “bereit”
gemacht.
1
oder auch eines synchronisierten Blocks
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-432
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Zustandsübergänge II
Ein Aufruf von sleep(n) signalisiert dem OS, dass der Thread
ungefähr n-Millisekunden als “schlafend” pausieren möchte.
Nach dieser Zeit wird der Thread wieder “bereit” gemacht.
Wichtig: Alle Locks werden weiterhin gehalten und können
andere Threads während des Schlafs blockieren!
Beim Aufruf der Methode obj.wait() wird der aufrufende
Thread “wartend” gemacht und in die Menge der wartenden
Threads des Objekts obj eingefügt. Das gehaltene Lock von
obj wird solange freigegeben.
Beim Aufruf von obj.notifyAll() bei einem Objekt werden
alle “wartenden” Threads bei diesem Objekt in den Zustand
“blockiert” versetzt und in die Menge der blockierten Threads
des Objektes eingereiht.
Ein Interrupt beendet “schlafend” und “wartend” ebenfalls:
ein “schlafender” Thread wird “bereit” gemacht;
ein “wartender” Thread wird “blockiert”.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-433
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Beispiel
public synchronized void deposit(double amount) {
System.out.println("Depositing " + amount);
double newBalance = balance + amount;
System.out.println("New balance is: " + newBalance);
balance = newBalance;
this.notifyAll();
}
public synchronized void withdraw(double amount)
throws InterruptedException {
while (balance < amount)
this.wait();
System.out.println("Withdrawing " + amount);
double newBalance = balance - amount;
System.out.println("New balance is: " + newBalance);
balance = newBalance;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-434
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Bemerkungen
Jetzt wartet ein Thread mit einer Abhebungen bei zu geringem
Kontostand; nach jeder Einzahlung durch andere Threads wird
geprüft, ob die Abhebung nun möglich ist und ggf. weiter gewartet.
wait meistens in Schleife aufrufen: nach dem Warten muss die
erwartete Bedingung noch nicht gelten zu geringe Einzahlung;
anderer wartender abhebender Thread war schneller, etc.
Bei Benutzung von wait das notifyAll an anderer Stelle
nicht vergessen!
wait/notifyAll dürfen nur aufgerufen werden, wenn man das
Lock des entsprechenden Objektes hält, also in synchronisierter
Methode/Block; sonst IllegalMonitorStateException.
Warum werden wartende Threads nicht einfach blockiert?
Antwort: dann würden sie ständig bereitgestellt und sofort
wieder blockiert.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-435
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Notify
Es gibt auch die Methode notify. Hier wird einer der wartenden
Threads zufällig ausgewählt und in den Zustand “blockiert”
versetzt. Von der Benutzung wird abgeraten.
Problem
Es kann sein, dass die Bedingung des jeweils zufällig ausgewählten
Threads nicht erfüllt ist und dieser sofort wieder “wartend” wird,
aber ein anderer wartender Thread den Fortschritt des Programms
sicherstellen könnte. In diesem Fall muss auf ein erneutes notify
von einem anderen Thread gewartet werden — und dann könnte
erneut ein unpassender “wartender” Thread ausgewählt werden.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-436
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Komplizierte Deadlocks
Nicht alle Deadlocks lassen sich durch wait und notifyAll
verhindern.
Es gebe drei Konten: account0, account1, account2 und drei
Threads:
th0 überweist immer wieder jeweils EUR500
von account1 und account2 auf account0.
th1 überweist immer wieder jeweils EUR500
von account0 und account2 auf account1.
th2 überweist immer wieder jeweils EUR500
von account0 und account1 auf account2.
Man beginnt mit EUR1000 in allen drei Konten.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-437
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Weitere Probleme bei Nebenläufigkeit
Bei nebenläufigen Berechnungen mit mehreren Threads können
neben Race Conditions und Deadlocks u.a. auch noch folgende
Probleme auftreten:
Livelock Es werdem zwar noch Threads ausgeführt und der
Zustand der Objekte ändert sich, aber diese reagieren nur noch
gegenseitig aufeinander, ohne einen echten Fortschritt zu
erzielen. Z.B. wird ein Warten nur durch Erledigung unwichtiger
Aufgaben unterbrochen.
Starvation Ein Spezialfall eines Livelocks: Ein wichtiger Thread
bleibt dauerhaft blockiert/wartend, weil ständig andere Threads
zum Vorzug kommen, diese aber nichts Wesentliches zum
Fortschritt des Programms beitragen.
Livelocks entstehen häufig bei fehlerhaften Versuchen der
Deadlock-Vermeidung: z.B. wenn alle Threads ein drohendes
Deadlock gleichzeitig erkennen und alle zurücktreten und es dann
später gleichzeitig erneut versuchen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-438
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Nebenläufigkeit und GUIs
Problem
Wird der GUI-Thread mit Berechnungen ausgelastet, reagiert die
GUI nicht mehr auf den Benutzer.
Größenänderung, Abbrechen-Knopf, etc.
Aufwendige Berechnungen sind also nebenläufig auszuführen.
Allerdings ist der JavaFX-Szenengraph nicht Thread-sicher, d.h.
greift man aus einem anderen Thread auf den Szenengraph von
JavaFX zu, können Synchronisationsfehler auftreten.
Unproblematisch, so lange nicht direkt gezeichnet wird.
Abhilfe
JavaFX bietet im Paket javafx.concurrent spezielle Versionen
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-439
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Entwurfsmuster: Guarded-Block
Ein Thread wartet auf die Erfüllung einer Bedingung durch
andere(n) Thread(s):
synchronized(lock) {
while(!condition) { lock.wait(); }
}
Threads, deren Ausführung möglicherweise eine Bedingung erfüllen
können, benachrichtigen alle wartenden Threads:
synchronized(lock) {
condition = true;
lock.notifyAll();
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-440
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Nebenläufigkeit: Zusammenfassung
Threads sind Programmstücke, die parallel mit dem Rest des
Programms ausgeführt werden (nebenläufig).
Mit der Methode start wird ein Thread gestartet. Seine
run-Methode wird dann nebenläufig abgearbeitet.
Threads können unterbrochen werden; dies kann mit
isInterrupted festgestellt werden.
Race Condition: das beobachtbare Programmverhalten hängt
von der Verzahnung der Threads ab.
Zu einem gegebenen Zeitpunkt kann bei einem Objekt nur eine
seiner synchronisierten Methoden abgearbeitet werden. Diese
wird dann als Ganzes abgearbeitet.
wait: der aufrufende Thread wird blockiert, bis ein anderer
Prozess bei dem entsprechenden Objekt notifyAll aufruft.
Ein Deadlock liegt vor, wenn Threads gegenseitig aufeinander
warten. Mit wait und notifyAll lassen sich Deadlocks in
manchen Fällen vermeiden.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-441
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Probleme bei Nebenläufigkeit
Bei nebenläufigen Berechnungen mit mehreren Threads können u.a.
folgende Probleme auftreten:
Race-Condition Das Ergebnis der Berechnung hängt von der
Verzahnung der Threads ab.
Deadlock Ein Thread wartet auf das Ergebnis eines anderen
Threads, welche direkt oder indirekt selbst auf das Ergebnis des
ersten Threads wartet. Beide warten aufeinander, die
Berechnung kommt somit zum erliegen.
Durch den Einsatz von Synchronisation/Locks kann man
Race-Conditions vermeiden, erhöht aber prinzipiell die Gefahr von
Deadlocks.
Verschiedene Threads können sich gegenseitig beeinflussen.
Manchmal wird ein Thread schneller als ein anderer abgehandelt.
Da die Möglichkeiten der Verzahnung immens sind, ist das
Gesamtergebnis der Berechnung kaum vorhersagbar/testbar.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-442
Nebenläufigkeit
Grundlagen Threadunterbrechung Synchronisation Zusammenfassung
Wichtige Methoden der Klasse Thread
Statisch
Thread.currentThread() liefert eigenes Thread Objekt
Thread.sleep(long millis) wartet Millisekunden ab
Dynamisch Dynamische Methoden, Aufruf über Thread Objekt
thread.join() wartet, bis thread beendet ist
thread.interrupt() um Unterbrechung zu signalisieren
thread.isInterrupted() ob ein Interrupt vorliegt
Wenn man nicht von Thread geerbt hat, kann man über die
statische Methode Thread.currentThread() das Thread-Objekt
erhalten und dann damit die dynamischen Methoden aufrufen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Nebenläufigkeit
11-443
Algorithmik: Sortieren und Suchen
Sortieren Schnittstelle Comparable Typvariablen Binäre Suche Rekursion Quickso
Einführung in die Programmierung
mit Java
Teil 12: Algorithmik: Sortieren und Suchen
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
12. Januar 2016
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-444
Algorithmik: Sortieren und Suchen
Sortieren Schnittstelle Comparable Typvariablen Binäre Suche Rekursion Quickso
Inhalt Teil 12: Algorithmik: Sortieren und
Suchen
46
Sortieren
durch Auswählen
Laufzeitanalyse
durch Mischen
Laufzeit
47
Vergleichen beliebiger Objekte: die
Schnittstelle Comparable
48
Typvariablen
49
Binäre Suche
50
Rekursion
51
Quicksort
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-445
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Sortieren durch Auswählen
Wir wollen ein Array a von int-Zahlen der Größe nach sortieren:
Z.B. 11, 9, 17, 5, 12 soll 5, 9, 11, 12, 17 werden.
Wir suchen das kleinste Element, hier a[3] = 5, und schaffen es
nach vorne durch Vertauschen mit dem ersten Element:
11
5
9 17
9 17
5
11
12
12
Dann suchen wir das kleinste Element von a[1..4]. Es ist schon
an der richtigen Stelle.
Dann das kleinste Element von a[2..4]. Es ist a[3]=11.
Vertauschen mit a[2] führt auf
5 9
11
17
12
Das kleinste Element von a[3..4] wird noch mit a[3] vertauscht
und wir sind fertig.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-446
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Dasselbe in Java
Zunächst das Testprogramm
import ...;
public class SelSortTest
{ public static void main(String[] args)
{ int[] a = ArrayUtil.randomIntArray(20, 100);
}
}
ArrayUtil.print(a);
SelSort.sort(a);
ArrayUtil.print(a);
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-447
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Sortieren durch Auswählen in Java
public class SelSort
{ /**
Finds the smallest element in an array range.
@param a the array to search
@param from the first position in a to compare
@return the position of the smallest element in the
range a[from]...a[a.length - 1]
*/
public static int minimumPosition(int[] a, int from)
{ int minPos = from;
for (int i = from + 1; i < a.length; i++)
if (a[i] < a[minPos]) minPos = i;
return minPos;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-448
Sortieren
}
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
/**
Sorts an array.
@param a the array to sort
*/
public static void sort(int[] a)
{ for (int n = 0; n < a.length - 1; n++)
{ int minPos = minimumPosition(a, n);
if (minPos != n)
ArrayUtil.swap(a, minPos, n);
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-449
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Testen
mhofmann> java sorting/SelSortTest
52 23 37 65 79 95 21 27 12 12 78 66 66 51 7 39 81 86 95 74
7 12 12 21 23 27 37 39 51 52 65 66 66 74 78 79 81 86 95 95
Für längere Arrays die print Statements ’rauskommentieren.
Bis Größe 10000 ist die Laufzeit im Millisekundenbereich.
Bei 100000 dauert es drei Sekunden.
Bei 500000 dauert es über eine Minute.
Bei 5000000 dauert es mehrere Stunden.
Wir führen eine genauere empirische Analyse durch:
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-450
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Stoppuhr
Die Methode System.currentTimeMillis() liefert die Anzahl der
Millisekunden, die seit 00:00 am 1.1.1970 verstrichen sind (ca. 1
Trillion > 231 daher ist long erforderlich.)
Damit können wir eine “Stoppuhr-Klasse” bauen, die die Methoden
reset()
start()
stop()
getElapsedTime()
bereitstellt (Details siehe Javadoc).
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-451
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Stoppuhr
package sorting;
/**
A stopwatch accumulates time when it is running. You can
repeatedly start and stop the stopwatch. You can use a
stopwatch to measure the running time of a program.
*/
public class StopWatch
{
private long elapsedTime;
private long startTime;
private boolean isRunning;
/**
Starts the stopwatch. Time starts accumulating now.
*/
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-452
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
public void start()
{ if (isRunning) return;
isRunning = true;
startTime = System.currentTimeMillis();
}
/**
Stops the stopwatch. Time stops accumulating and is
is added to the elapsed time.
*/
public void stop()
{ if (!isRunning) return;
isRunning = false;
long endTime = System.currentTimeMillis();
elapsedTime = elapsedTime + endTime - startTime;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-453
Sortieren
/**
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Returns the total elapsed time.
@return the total elapsed time
*/
public long getElapsedTime()
{ if (isRunning)
{ long endTime = System.currentTimeMillis();
elapsedTime = elapsedTime + endTime - startTime;
startTime = endTime;
}
return elapsedTime;
}
/**
Stops the watch and resets the elapsed time to 0.
*/
public void reset()
{ elapsedTime = 0;
isRunning = false;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-454
Sortieren
/**
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Constructs a stopwatch that is in the stopped state
and has no time accumulated.
*/
public StopWatch()
{ reset();
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-455
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Laufzeit von SelSort
public class SelSortTime
{ public static void main(String[] args)
{
int n = Integer.parseInt(JOptionPane.showInputDialog("Ente
array size:"));
int[] a = ArrayUtil.randomIntArray(n, 100);
StopWatch timer = new StopWatch();
timer.start();
SelSort.sort(a);
timer.stop();
System.out.println("Elapsed time: "
+ timer.getElapsedTime() + " milliseconds");
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-456
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Laufzeitmessung
n
500
1000
1500
2000
2500
3000
3500
10K
20K
30K
40K
100K
Laufzeit in ms (’03)
7
14
27
54
66
93
133
992
3939
8848
15858
Martin Hofmann, Steffen Jost
Laufzeit in ms (’12)
5
7
12
15
18
23
28
95
356
918
1282
8176
Einführung in die Programmierung
Laufzeit in ms (’16)
4
14
19
15
19
29
42
61
146
270
518
2756
Algorithmik: Sortieren und Suchen
12-457
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Analyse der Laufzeit
Als grobes Maß für die Laufzeit wählen wir die Anzahl der
Arrayzugriffe.
Die wirkliche Laufzeit ist auf jeden Fall größer als ein festes
Vielfaches dieser Zahl.
Wir schätzen die Zahl der Arrayzugriffe von unten ab:
Sei n die Arraygröße.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-458
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Abschätzung der Laufzeit
Finden des kleinsten Elements: n Zugriffe.
Finden des 2.kleinsten Elements: n − 1 Zugriffe.
Finden des 3.kleinsten Elements: n − 2 Zugriffe.
Finden des n − 1.kleinsten Elements: 2 Zugriffe.
−1=
Macht zusammen 2 + 3 + 4 + 5 + · · · + n = n(n+1)
2
1 2
1
n
+
n
−
1.
2
2
Das Vertauschen haben wir gar nicht gerechnet!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-459
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Größenordnung der Laufzeit
Zahl der Arrayzugriffe ≥ 12 n2 + 12 n − 1.
Der lineare Term spielt für große n keine Rolle.
Der Faktor 1/2 auch nicht, da die exakte Laufzeit sowieso durch
Multiplikation mit einem maschinen- und
implementationsabhängigen Wert entsteht.
Nur das quadratische Wachstum interessiert. Wir schreiben
1
1 2
2
2 n + 2 n − 1 = O(n ).
Die Laufzeit von Selection Sort ist O(n2 )
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-460
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
O-Notation
Zur Bestimmung der O-Notation finde man den am schnellsten
wachsenden Term und lasse eventuelle Vorfaktoren weg.
0, 9n3 + 890n2 = O(n3 )
n2 (n2 + 4n) = O(n4 )
2n + n30000 = O(2n )
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-461
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Für Pedanten
Eigentlich müsste man schreiben
1 2
n ∈ O(n2 )
2
denn O(n2 ) ist die Klasse der Funktionen von höchstens
quadratischem Wachstum.
Das Gleichheitszeichen hat sich aber eingebürgert.
Formale Definition von O(−) gibt es in “Algorithmen und
Datenstrukturen”.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-462
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Sortieren durch Mischen
Gegeben folgendes Array der Größe 10.
5, 9, 10, 12, 17,
1, 8, 11, 20, 32
Die beiden “Hälften” sind hier bereits sortiert!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-463
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Mischen
Wir können das Array sortieren, indem wir jeweils von der ersten
oder der zweiten Hälfte ein Element wegnehmen, je nachdem,
welches “dran” ist:
5, 9, 10, 12, 17,
6 5, 9, 10, 12, 17,
6 5, 9, 10, 12, 17,
6 5, 6 9, 10, 12, 17,
...
6 1, 8, 11, 20, 32
6 1, 8, 11, 20, 32
6 1, 6 8, 11, 20, 32
6 1, 6 8, 11, 20, 32
...
1
1, 5
1, 5, 8
1, 5, 8, 9
. . . und die weggenommenen Elemente in ein anderes Array
kopieren.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-464
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Sortieren durch Mischen
Falls die beiden Hälften nicht schon sortiert sind, dann müssen
wir sie eben vorher sortieren.
Wie? Durch Mischen der jeweiligen Hälften (also Viertel).
Und wenn die nicht schon sortiert sind? Dann werden
wiederum die jeweiligen Hälften (also Achtel) gemischt.
Usw. bis man bei Arrays der Größe Eins angelangt ist, die ja
stets sortiert sind.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-465
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Sortieren durch Mischen in Java
public static void mergeSort(int[] a, int from, int to)
{ if (from >= to) return;
int mid = (from + to) / 2;
// sort the first and the second half
mergeSort(a, from, mid);
mergeSort(a, mid + 1, to);
merge(a, from, mid, to);
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-466
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Mischen
public static void merge(int[] a,
int from, int mid, int to)
{ int n = to - from + 1;
// size of the range to be merged
// merge both halves into a temporary array b
int[] b = new int[n];
int i1 = from;
// next element to consider in the first range
int i2 = mid + 1;
// next element to consider in the second range
int j = 0;
// next open position in b
// as long as neither i1 nor i2 past the end, move
// the smaller element into b
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-467
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
while (i1 <= mid && i2 <= to)
{ if (a[i1] < a[i2])
{ b[j] = a[i1];
i1++;
}
else
{ b[j] = a[i2];
i2++;
}
j++;
}
// copy any remaining entries of the first half
while (i1 <= mid)
{ b[j] = a[i1];
i1++;
j++;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-468
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
// copy any remaining entries of the second half
while (i2 <= to)
{ b[j] = a[i2];
i2++;
j++;
}
}
// copy back from the temporary array
for (j = 0; j < n; j++)
a[from + j] = b[j];
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-469
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Laufzeit von Merge Sort
n
500
3500
10K
20K
30K
40K
50K
60K
80K
5M
10M
100M
Laufzeit in ms ’03
7
17
35
41
56
69
94
109
138
9612
Martin Hofmann, Steffen Jost
Laufzeit in ms ’08
1
15
32
43
49
45
62
59
114
2219
Einführung in die Programmierung
Laufzeit in ms ’16
3
10
4
14
23
27
29
31
29
583
1117
11711
Algorithmik: Sortieren und Suchen
12-470
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Analytische Bestimmung der Laufzeit
Sei T (n) die Zahl der Arrayzugriffe von Merge Sort bei
Arraygröße n.
Es gilt:
T (1) = 0
T (n) = 2T ( n2 ) + 5n
falls n = 2k (ansonsten stimmt es immer noch
“größenordnungsmäßig”).
Die 5n kommen vom Mischen: 3n fürs eigentliche Mischen, 2n
fürs Zurückschreiben.
Lösung der Gleichung:
T (n) = T (2k ) = 5 · 2k + 2T (2k−1 ) =
5 · 2k + 2 · 5 · 2k−1 + 4T (2k−2 ) + · · · + 2k · T (1)
Wir raten: T (2k ) = k · 5 · 2k + 2k · T (1) = k · 5 · 2k .
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-471
Sortieren
durch Auswählen Laufzeitanalyse durch Mischen Laufzeit
Gegenprobe: k · 5 · 2k = 2(k − 1) · 5 · 2k−1 + 5 · 2k .
Also gilt T (2k ) = 5 · 2k · k oder T (n) = 5n log2 n.
Es ist: 5n log2 (n) = O(n log(n)).
Die Basis lässt man weg, da alle Logarithmen proportional sind.
Die Laufzeit von Merge Sort ist O(n log(n))
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-472
Schnittstelle Comparable
Die Schnittstelle Comparable
Wir wollen Such- und Sortieroperationen für beliebige Objekte
definieren.
Dazu verwenden wir die vordefinierte Schnittstelle Comparable:
public interface Comparable {
int compareTo(Object other);
}
Wenn o:Comparable und other:Object und o mit other
vergleichbar ist, dann sollte gelten
o.compareTo(other) < 0, falls o kleiner other
o.compareTo(other) = 0, falls o gleich other
o.compareTo(other) > 0, falls o größer als other
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-473
Schnittstelle Comparable
Beispiele
Die Klasse String implementiert automatisch die Schnittstelle
Comparable.
Die Ordnung ist dabei die lexikographische Ordnung.
Der Ausdruck
"AAAaaa".compareTo("Mein Schluesseldienst") hat einen
Wert < 0.
Der Ausdruck "AAAaaa".compareTo(new Point(2,3)) ist
typkorrekt (warum?) führt aber zu einem Laufzeitfehler
(Programmabbruch).
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-474
Schnittstelle Comparable
Beispiele
Bankkonten nach ihrer Kontonummer sortieren:
public class BankkontoAngeordnet extends
Bankkonto implements Comparable {
public int compareTo(Object other) {
return getKontonummer() ((Bankkonto)other).getKontonummer();
}
}
Will man verschiedene Anordnungen, so verwende man das
Strategiemuster. Siehe auch Comparator.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-475
Schnittstelle Comparable
Anwendung im Beispiel
Will man andere Objekte als Zahlen sortieren, so schreibe man also
mergeSort(Comparable[] a, int from, int to){...}
und ersetze im Code jeweils x < y durch x.compareTo(y) < 0.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-476
Typvariablen
Typvariablen
In der Java Dokumentation steht:
public interface Comparable<T> {
public int compareTo(T o);
}
Was bedeutet das?
Es handelt sich um eine parametrisierte Schnittstelle.
Die Schnittstelle Comparable<Integer> deklariert eine Methode
public int compareTo(Integer o);
Die Schnittstelle Comparable<Student> deklariert eine Methode
public int compareTo(Student o);
Die Schnittstelle Comparable ist eine Abkürzung für
Comparable<Object>.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-477
Typvariablen
Anwendung
Versucht man
public static <T> void merge(Comparable<T> a[],
int from, int mid, int to){
... if(a[i1].compareTo(a[i2])<0) ...
}
so kommt
compareTo(T) in java.lang.Comparable<T> cannot be
applied to (java.lang.Comparable<T>)
{ if (a[i1].compareTo(a[i2])<0)
Das Argument b in a.compareTo(b) muss vom Typ T sein, wenn a
vom Typ Comparable<T> ist.
Beachte: Das vorgestellte <T> bezeichnet, dass die Deklaration
durch T parametrisiert ist. Für jede konkrete Einsetzung von T
ergibt sich ein anderer Typ. (“Typschema”)
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-478
Typvariablen
Lösung: Constraints und Wildcards
Korrekterweise deklariert man
public static <T extends Comparable<T>> void
mergeSort(T[] a, int from, int to){ ... }
oder
public static <T extends Comparable<T>> void
mergeSort(ArrayList<T> a, int from, int to){ ... }
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-479
Typvariablen
Wildcards
In der Dokumentation findet sich sogar:
<T extends Comparable<? super T>>mergeSort(ArrayList<T> a,
int from, int to){ ... }
T muss also ein Subtyp von Comparable<S> sein,
wobei S ein Supertyp von T ist.
Das ? ist eine Wildcard, sie steht für irgendeinen Typ, der die
Bedingung (formuliert mit super oder extends) erfüllt:
? super T: ein Supertyp von T
? extends T: ein Subtyp von T
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-480
Typvariablen
Generische Klassen
class Klassenname <Typvariable,...,Typvariable > { ... }
Beispiel:
public class Box<T> {
public T inhalt;
}
public T tauscheInhalt(T neu){
T alt = this.inhalt;
this.inhalt = neu;
return alt;
}
Instantiierung generischer Typen durch Angabe in spitze
Klammer: z.B. Box<Rechteck> boxrecht; oder
Box<Integer> boxint;
Erben können generisch sein oder auch nicht.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-481
Typvariablen
Generische Methoden
<Typvariable,...,Typvariable > Ergebnistyp Methodenname
(Parameter,...,Parameter ) { ... }
Beispiel:
public static <T> T wahl(boolean b, T x, T y) {
T result;
if (b) { result = x; }
else { result = y; }
return result;
}
Generische Methoden werden ganz normal aufgerufen,
d.h. ohne Erwähnung des Parametertyps:
String s = wahl(true, "EiP=einfach", "EiP=schwierig");
Vor allem für statische Methoden
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-482
Typvariablen
Generische Typen
Generische Deklarationen ermöglichen bessere Typprüfung für
generischen Code:
static <T> T wahl(boolean b, T x, T y) =⇒
String t = wahl(false, "Sonne", "Regen" ); X
Object t = wahl(true, "EiP einfach", 666);
static Object wahl'(boolean b, Object x, Object y)
String t = wahl'(false, "Sonne", "Regen" );
Object t = wahl'(true, "EiP einfach", 666); X
Die erste Situation ist fast immer die gewünschte!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-483
Typvariablen
Geschichte Generische Typen in Java
1997 Martin Odersky, Philip Wadler: Pizza.
“Eine funktionale Spracherweiterung von Java mit
generischen Typen”
Principles of Programming Languages Conference,
POPL’97
1998 Gilad Bracha, Martin Odersky, David Stoutamire,
Philip Wadler.
Making the future safe for the past: Adding Genericity
to the Java Programming Language, OOPSLA’98
„Beschreibt Generic Java (GJ) als Weiterentwicklung
von Pizza“
2004 Java 1.5 mit generischen Typen
“Making Java easier to type, and easier to type”
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-484
Typvariablen
Generische Vererbung
Erben können generisch sein oder auch nicht:
public
public
public
public
class
class
class
class
A<X,Y> {...}
B<X,Y> extends A<X,Y> {...}
C<Y> extends A<Rechteck,Y> {...}
D extends A<Shape,Rechteck> {...}
Aus S erbt von T folgt nicht, dass A<S> ein Subtyp von A<T>
ist:
Box<Point> pBox = new Box<Point>(new Point(1,12));
Box<Object> oBox;
oBox = pBox;
// Typfehler!
oBox.tausche(new GraphicsWindow());
Point s = pBox.inhalt;
//
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-485
Typvariablen
Anwendung
Versucht man
public static <T> void merge(Comparable<T> a[],
int from, int mid, int to){
... if(a[i1].compareTo(a[i2])<0) ...
}
so kommt
compareTo(T) in java.lang.Comparable<T> cannot be
applied to (java.lang.Comparable<T>)
{ if (a[i1].compareTo(a[i2])<0)
Das Argument b in a.compareTo(b) muss vom Typ T sein, wenn a
vom Typ Comparable<T> ist.
Beachte: Das vorgestellte <T> bezeichnet, dass die Deklaration
durch T parametrisiert ist. Für jede konkrete Einsetzung von T
ergibt sich ein anderer Typ. (“Typschema”)
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-478
Typvariablen
Lösung: Constraints und Wildcards
Korrekterweise deklariert man
public static <T extends Comparable<T>> void
mergeSort(T[] a, int from, int to){ ... }
oder
public static <T extends Comparable<T>> void
mergeSort(ArrayList<T> a, int from, int to){ ... }
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-479
Typvariablen
Wildcards
In der Dokumentation findet sich sogar:
<T extends Comparable<? super T>>mergeSort(ArrayList<T> a,
int from, int to){ ... }
T muss also ein Subtyp von Comparable<S> sein,
wobei S ein Supertyp von T ist.
Das ? ist eine Wildcard, sie steht für irgendeinen Typ, der die
Bedingung (formuliert mit super oder extends) erfüllt:
? super T: ein Supertyp von T
? extends T: ein Subtyp von T
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-480
Typvariablen
Wildcard Beispiel
Angenommen wir haben:
Bankkonto implements Comparable<Bankkonto>
Sparkonto extends Bankkonto
Damit gilt auch:
Sparkonto extends Comparable<Bankkonto>
Sparkonto extends Comparable<? super Sparkonto>
Es gilt aber gerade nicht:
Sparkonto extends Comparable<Sparkonto>
Denn Sparkonto implementiert dieses Interface ja nicht selbst!
Deshalb Wildcards verwenden, wie z.B.
<T extends Comparable<? super T>>
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-486
Binäre Suche
Binäre Suche
Um in einer bereits sortierten Array zu suchen, bietet sich die
effiziente binäre Suche an (O(log n)):
public static boolean sucheVonBis(
Comparable[] l, Object w, int i, int j)
{
if (i > j) return false;
if (i == j) return 0 == l[i].compareTo(w);
int m = (i+j) / 2;
Comparable wm = l[m];
int comp = wm.compareTo(w);
if (comp == 0) return true;
if (comp < 0) // wm < w
return sucheVonBis(l,w,m+1,j);
else
return sucheVonBis(l,w,i,m-1);
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-487
Binäre Suche
Verbesserte Typisierung
Die gezeigte Signatur ist problematisch:
public static boolean sucheVonBis(
Comparable[] l, Object w, int i, int j)
1
2
Warum ist diese Signatur denn problematisch?
Man gebe eine verbesserte Typisierung mit Typvariablen und
Wildcards an!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-488
Rekursion
Rekursion
Den Aufruf einer Methode in ihrem eigenen Rumpf bezeichnet man
BinäreSuche
als Rekursion.
Erinnerung: Mischen
public static void f() {
f();
}
Rekursion bietet sich immer dann an, wenn man die Lösung eines
Problems auf die Lösung gleichartiger aber kleinerer Teilprobleme
zurückführen kann.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-489
Rekursion
Rekursion
Den Aufruf einer Methode in ihrem eigenen Rumpf bezeichnet man
BinäreSuche
als Rekursion.
Erinnerung: Mischen
Rekursion sollte irgendwann zum Ende kommen:
public static void f() {
if (!ende) {
f();
}
}
Rekursion bietet sich immer dann an, wenn man die Lösung eines
Problems auf die Lösung gleichartiger aber kleinerer Teilprobleme
zurückführen kann.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-489
Rekursion
Weitere Beispiele von Rekursion
Aufzählungsverfahren (alle Permutationen, Pflasterungen, ...)
Ackermannfunktion:
A(0, y ) = y + 1
A(x + 1, 0) = 1
A(x + 1, y + 1) = A(x, A(x + 1, y ))
Türme von Hanoi
QuickSort
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-490
Rekursion
Weitere Beispiele von Rekursion
Aufzählungsverfahren (alle Permutationen, Pflasterungen, ...)
Ackermannfunktion:
A(0, y ) = y + 1
A(x + 1, 0) = 1
A(x + 1, y + 1) = A(x, A(x + 1, y ))
Türme von Hanoi
QuickSort
Merke:
Will man zeigen, dass eine rekursive Methode eine Spezifikation
erfüllt (Vor- und Nachbedingung), so darf man dabei annehmen,
dass rekursive Aufrufe im Rumpf der Methode diese bereits erfüllen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-490
Rekursion
Zusammenfassung
Verschiedene Algorithmen (Rechenverfahren) für das gleiche
Problem können sich drastisch in der Laufzeit unterscheiden.
Die O-Notation gestattet es, Angaben über die Größenordnung
einer Funktion, z.B. der Laufzeit zu machen.
Selection Sort ist ein O(n2 ) Verfahren, Merge Sort ist ein
O(n log(n)) Verfahren zum Sortieren von Arrays. Merge Sort
ist auch empirisch wesentlich performanter.
Typvariablen und Wildcards erlauben präzisere Typisierung
unter weitgehender Vermeidung von Object und typecast.
Rekursive Verfahren beruhen auf der Zerlegung eines Problems
in kleinere gleichartige Probleme.
Formal bedeutet Rekursion den Aufruf einer Methode in ihrem
eigenen Rumpf.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-491
Quicksort
Quicksort
Ein von Hoare erstmals beschriebenes Sortierverfahren mit
quadratischer Laufzeit im schlechtesten Fall, aber exzellenter
mittlerer Laufzeit. In der Praxis für die meisten Anwendungen das
beste Verfahren.
Idee: teile zu sortierenden Bereich in obere (O) und untere Hälfte
(U), sodass alle Elemente von O oberhalb (in der Ordnung) von
allen Elementen von U liegen.
Es gelte, a[p..r] zu sortieren (gemäß einem Comparator).
Wähle Pivotelement x.
Arrangiere a um und bestimme q, sodass
a[p..q]≤ x ≤a[q+1..r]. (Partitionieren)
Sortiere rekursiv a[p..q] und a[q+1..r].
Beachte: damit ist a[p..r] sortiert.
Schwierigkeiten: 1. das Umarrangieren. 2. die Termination
(Sicherstellen, dass p ≤ q und q < r.)
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-492
Quicksort
Partitionieren
public static int partition(Object[] a,
if (p==r) {
if (c.compare(a[p],x) <= 0)
{return p; }
else
{return p-1;}
}
else if (c.compare(a[p],x) < 0)
else if (c.compare(a[r],x) > 0)
else {
Object hilf = a[p];
a[p] = a[r];
a[r] = hilf;
return partition(a, p, r-1,
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
int p, int r, Object x,
return partition(a,p+1,r
return partition(a,p,r-1
x, c);
Algorithmik: Sortieren und Suchen
12-493
Quicksort
Korrektheit
Die Vorbedingung lautet p ≤ r.
Nach dem Aufruf
q = partition(a,p,r,x,c);
gilt folgendes:
Die Reihenfolge der Einträge von a[p..r] wurde verändert.
Es gilt a[p..q]≤ x ≤a[q+1..r] (im Sinne des Komparators
c).
Es gilt p − 1 ≤ q ≤ r.
Existiert in a[p..r] ein Element y mit y ≤ x, so gilt p ≤ q.
Existiert in a[p..r-1] ein Element y mit y ≥ x, so gilt
zusätzlich q < r.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-494
Quicksort
Sortieren
Die Sortierfunktion ist jetzt ganz einfach:
public static void sort(Object[] a, int p, int r, Comparator c)
if (p==r) ;
else {
int q = partition(a, p, r, a[p], c);
sort(a,p,q,c);
sort(a,q+1,r,c);
}
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-495
Quicksort
Bewertung
Die Laufzeit von Quicksort ist im Mittel O(n log n), wobei
n = r − p.
Im schlechtesten Fall ist die Laufzeit aber O(n2 ) (eine der beiden
Partitionen hat immer die Größe eins).
Der große Vorteil von Quicksort gegenüber z.B. Mergesort (Info I)
liegt darin, dass nach den beiden rekursiven Aufrufen keine
Nachbearbeitung mehr erforderlich ist.
In der Praxis ist Quicksort (nach einigen Verbesserungen) das
effizienteste Sortierverfahren.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-496
Quicksort
Effizienzsteigerung von Quicksort
Iterative Implementierung von partition mit While-Schleifen.
(Bei effizienten Compilern nicht erforderlich, da partition ja
bereits endrekursiv ist.)
Zufällige Wahl des Pivotelements oder gar:. . .
. . . Wahl des Pivotelements als das mittlere von drei zufällig
ausgewählten.
Aber Vorsicht: man darf nicht das letzte Element als Pivot
wählen (wg. Folie 493, Korrektheit). Besser durch Vertauschen
das gewünschte Pivotelement in die Position a[0] bringen und
dann mit a[0] als Pivot fortfahren.
Verwendung eines anderen Sortierverfahrens bei n < 10.
Dadurch weniger Verwaltungsaufwand an den Blättern des
Rekursionsbaumes.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Algorithmik: Sortieren und Suchen
12-497
Einführung in die Programmierung
mit Java
Teil 13: Verkettete Listen
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
19. Januar 2016
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-498
Inhalt Teil 13: Verkettete Listen
52
Vorgefertigte Listen in Java
53
Iteratoren
54
Implementierung verketteter Listen
55
Doppelt verkettete Listen
56
Zusammenfassung
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-499
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Verkettete Listen
Eine verkettete Liste besteht (wie eine Kette) aus einzelnen
Gliedern.
Jedes Glied enthält ein Datum, sowie einen Verweis auf das
nächste Glied, eventuell einen zusätzlichen Verweis auf das
vorhergehende Glied.
Verkettete Listen dienen zur Verwaltung von Daten variabler
Anzahl, auf die in der Regel sequentiell zugegriffen wird.
Sie erlauben das Einfügen eines Elements an beliebiger Stelle
in konstanter Zeit.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-500
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Die Klasse LinkedList<E>
Die Klasse java.util.LinkedList<E> implementiert verkettete
Listen.
Hierbei ist E ähnlich wie bei ArrayList<E> ein Typparameter.
Unter anderem gibt es die folgenden Methoden:
void
void
E
E
E
E
addFirst(E obj)
addLast(E obj)
getFirst()
getLast()
removeFirst()
removeLast()
Weiter Methoden wie Einfügen an beliebiger Stelle werden über
einen Iterator bereitgestellt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-501
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Schnittstelle ListIterator<E>
Um auf Positionen innerhalb einer Liste zuzugreifen, gibt es in
LinkedList<E> die Methode ListIterator<E> listIterator()
welche einen Iterator liefert, der auf das erste Element zeigt.
Die Schnittstelle ListIterator<E> bietet u.a. folgende Methoden:
boolean hasNext()
gibt an, ob am Positionszeiger noch ein Element vorhanden ist.
E next()
liefert das Element am Positionszeiger zurück.
Fehler, falls hasNext() = false.
void add(E e)
fügt ein Element vor dem Positionszeiger ein.
void remove()
entfernt das Letzte, von next() zurückgegebene, Element.
void set(E e)
ersetzt das Letzte, von next() zurückgegebene, Element.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-502
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Anwendungsbeispiel
import java.util.*;
public class ListTest {
public static void main(String[] args) {
LinkedList<String> staff = new LinkedList<String>();
staff.addFirst("Tom");
staff.addFirst("Romeo");
staff.addFirst("Harry");
staff.addFirst("Dick");
ListIterator<String> iterator = staff.listIterator();
// |DHRT
iterator.next();
// D|HRT
iterator.next();
// DH|RT
iterator.add("Juliet");
// DHJ|RT
iterator.add("Nina");
// DHJN|RT
..
.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-503
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
..
.
}
iterator.add("Nina");
// DHJN|RT
iterator.next();
// DHJNR|T
iterator.remove();
// DHJN|T
iterator = staff.listIterator();
while (iterator.hasNext())
System.out.println(iterator.next());
}
Ausgabe:
Dick
Harry
Juliet
Nina
Tom
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-504
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Erklärung
Die Methode add fügt ein neues Element unmittelbar vor dem
Positionszeiger ein.
Die Methode remove ist nur zulässig, wenn direkt vorher next
aufgerufen wurde; dann wird das von next zurückgegebene
Element aus der Liste entfernt (“ausgespleißt”).
Es gibt auch noch die Methoden hasPrevious, previous,
die den Positionszeiger nach vorne bewegen.
Die Methode remove darf auch nach einem Aufruf von
previous aufgerufen werden und entfernt dann das von
previous zurückgegebene Element aus der Liste.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-505
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Iteratoren-Schleifen
Mit einem Iterator kann man bequem über eine List laufen:
Iterator<E> iter = list.listIterator();
while(iter.hasNext()){
E elem = iter.next();
// Code zur Bearbeitung von elem
}
Bemerkung
Dieses Muster ist so nützlich, dass es eine eigene Syntax bekommen
hat: die uns bereits bekannte For-Schleife:
for (E elem : list) { // Code zur Bearbeitung von elem }
For-Schleifen sind für alle Typen erlaubt, welche das Interface
Iterable<E> implementieren.
Nicht nur für Listen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-506
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Fallstricke Iteratoren
Während der Verwendung eines Iteratoren darf die
Datenstruktur, über die iteriert wird, nicht verändert werden.
Ausnahme ConcurrentModificationException wird sonst
geworfen. Dabei ist es unerheblich, welcher Thread die
Modifikation durchführt.
Ausnahme: Veränderungen durch den Iterator selbst, z.B. mit
remove oder set
Manche Implementierung unterstützen das Interface nicht
vollständig: UnsupportedOperationException wird dann bei
Aufrufen von remove oder set geworfen.
Aufruf von remove oder set ohne vorher next oder previous
aufgerufen zu haben, liefert Ausnahme
IllegalStateException.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-507
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Implementierung von LinkedList<E>
Die Klasse LinkedList<E> ist bereits implementiert.
Wir wollen sehen, wie das gemacht ist.
Braucht man nur die Methoden addFirst, getFirst, next,
hasNext, so kann man einfach verkettete Listen verwenden:
import java.util.*;
class Link<E> {
E data;
Link<E> next;
}
public class LinkedList<E> {
private Link<E> first;
public LinkedList<E>() {
first= null;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-508
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Link<E> getFirstLink(){return first;}
public ListIterator<E> listIterator() {
return new LinkedListIterator<E>(this);
}
public E getFirst() {
if (first==null)
throw new NoSuchElementException();
else return first.data;
}
public void addFirst(E obj) {
Link<E> newLink = new Link<E>();
newLink.data = obj;
newLink.next = first;
first = newLink;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-509
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
public E removeFirst() {
if (first==null)
throw new NoSuchElementException();
E obj = first.data;
first = first.next;
return obj;
}
}
class LinkedListIterator<E> implements ListIterator<E> {
private Link<E> position;
private LinkedList<E> list;
public LinkedListIterator(LinkedList<E> l) {
position = l.getFirstLink();
list = l;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-510
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
public boolean hasNext() {
return position != null;
}
}
/** Vorbedingung: hasNext() */
public E next() {
E obj = position.data;
position = position.next;
return obj;
}
/* Hier fehlen noch weitere Methoden
* des Interfaces ListIterator<E>
*/
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-511
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Erklärung
Ein Link<E> besteht aus einem Datum und einem Verweis auf
ein (das nächste) Link.
Eine Liste ist einfach ein Verweis auf ein Link<E>.
Die leere Liste wird durch null repräsentiert.
Die Klassen Link und LinkedListIterator werden von
außen nicht benötigt. Man kann sie daher auch als innere
Klassen realisieren. Dann ist zudem der Zugriff auf first in
LinkedListIterator einfacher.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-512
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Doppelt verkettete Listen
Will man auch die Methoden addLast, getLast, add,
remove, hasPrevious, previous implementieren, so muss
man die Möglichkeit haben, in einer Liste rückwärts zu gehen.
Dazu gibt man jedem Link auch noch einen Verweis auf das
vorhergehende Link mit. Man muss natürlich all diese Verweise
in den Methoden konsistent halten.
Eine Liste besteht nun aus zwei Verweisen: einem auf das erste
Link-Objekt und einen auf das Letzte. Die leere Liste wird
durch zwei Nullreferenzen repräsentiert.
Der Iterator wird nunmehr auch durch zwei Verweise (genannt
forward, backward) implementiert.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-513
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Implementierung
import java.util.*;
class Link<E> {
E data;
Link<E> next;
Link<E> prev;
}
public class LinkedList<E> {
private Link<E> first;
private Link<E> last;
Link<E> getFirstLink(){return first;}
public LinkedList() {
first= null;
last = null;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-514
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
public ListIterator<E> listIterator() {
return new LinkedListIterator<E>(this);
}
public E getFirst() {
if (first==null)
throw new NoSuchElementException();
else return first.data;
}
public E getLast() {
if (first==null)
throw new NoSuchElementException();
else return last.data;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-515
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
public void addFirst(E obj) {
Link<E> newLink = new Link<E>();
newLink.data = obj;
newLink.next = first;
newLink.prev = null;
if (first == null) {
first = newLink;
last = newLink;
} else {
first.prev = newLink;
first = newLink;
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-516
Verkettete Listen
}
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
public void addLast(E obj) {
Link<E> newLink = new Link<E>();
newLink.data = obj;
newLink.next = null;
newLink.prev = last;
if (first == null) {
first = newLink;
last = newLink;
} else {
last.next = newLink;
last = newLink;
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-517
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
class LinkedListIterator<E> //extends LinkedList<E>
implements ListIterator<E> {
private Link<E> forward;
private Link<E> backward;
private LinkedList<E> list;
private Link<E> lastReturned;
public LinkedListIterator(LinkedList<E> l) {
forward = l.getFirstLink;
backward = null;
list = l;
lastReturned = null;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-518
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
public void add(E obj) {
lastReturned = null;
if (backward == null) {
list.addFirst(obj);
backward = list.getFirstLink();
} else if (!hasNext()) {
list.addLast(obj);
backward = backward.next;
} else {
Link<E> newLink = new Link<E>();
newLink.data = obj;
newLink.next = forward;
newLink.prev = backward;
backward.next = newLink;
forward.prev = newLink;
backward = newLink;
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-519
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
public boolean hasNext() {
return forward != null;
}
public boolean hasPrevious() {
return backward!= null;
}
public E next() {
lastReturned = forward;
backward = forward;
forward = forward.next;
return backward.data;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-520
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
public E previous() {
lastReturned = backward;
forward = backward;
backward = backward.prev;
return forward.data;
}
public void set(E obj) {
lastReturned.data = obj;
}
public int nextIndex()
{ throw new UnsupportedOperationException(); }
public int previousIndex()
{ throw new UnsupportedOperationException(); }
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-521
Verkettete Listen
}
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
public void remove() {
if (lastReturned == null)
throw new IllegalStateException();
else {
if (lastReturned.prev == null)
list.removeFirst();
else if (lastReturned.next == null)
list.removeLast();
else {
lastReturned.prev.next = lastReturned.next;
lastReturned.next.prev = lastReturned.prev;
}
if (lastReturned == backward)
backward = lastReturned.prev;
else
forward = lastReturned.next;
lastReturned = null;
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-522
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Bemerkungen
Der Fall einer leeren Liste ist jeweils gesondert zu behandeln.
Es wurden nicht alle erforderlichen Methoden implementiert;
insbesondere nicht “remove”.
Die Instanzvariable lastReturned verweist auf das Glied, das
von next, bzw. prev als letztes zurückgegeben wurde, Es ist
null falls der letzte Aufruf nicht next oder previous war.
Man braucht sie zur Implementierung von remove (und set,
siehe Doku.)
Es gibt in der Literatur zahlreiche Varianten.
Listen können zur Realisierung von Stacks und Queues
verwendet werden.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-523
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Vergleich Listen, Arrays
Sowohl Listen, als auch Arrays speichern Folgen von Daten.
Listen sind besonders geeignet, falls Element an bestimmter
Stelle eingefügt oder entfernt werden müssen.
Listen haben keine feste Größe, sondern können beliebig
erweitert werden.
Arrays sind besonders geeignet, wenn der Zugriff auf Elemente
über Positionszahlen (Indices) erfolgt. Bei Listen verursachen
solche Operationen einen Aufwand, der proportional zum Index
ist.
Fazit
Verändert sich die Anzahl der Elemente oft, oder werden die
Elemente immer der Reihe nach bearbeitet, dann LinkedList;
Ändert sich die Anzahl der Elemente kaum (oder steht diese
vorab fest), und werden die Elemente möglicherweise in
willkürlicher Reihenfolge benutzt, dann ArrayList.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-524
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Java-Arrays sind nicht Typ-sicher
Folgender Code wird vom Compiler (und IDEs) problemlos
akzeptiert, liefert aber einen Laufzeitfehler:
Integer[] iary = new Integer[] {0,1,2,3,4};
Object[] objs = iary; // Fälschlicherweise erlaubt
String s = "Buwharhar!";
objs[2] = s;
// java.lang.ArrayStoreException
Das Problem ist historisch bedingt:
Vor Java 1.5 gab es keine Generics. Zuweisung wie in der zweiten
Zeile waren bis dahin einen mangelhafter, aber weit verbreiteter und
auch durchaus praktikabler Ersatz:
void sort(Object[] ary) // Sortiere beliebiges Array
Daher war es nicht sinnvoll, solchen Code plötzlich zu verbieten.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-525
Verkettete Listen
LinkedList Iteratoren Implementierung Doppelte Verkettung Zusammenfassung
Übung
Was wird gedruckt?:
LinkedList<String> staff = new LinkedList<String>();
ListIterator<String> iterator = staff.listIterator();
iterator.add("Tom");
iterator.add("Dick");
iterator.add("Harry");
iterator = staff.listIterator();
iterator.next();
iterator.next();
iterator.add("Romeo");
iterator.next();
iterator.add("Juliet");
iterator = staff.listIterator();
iterator.next();
iterator.remove();
while(iterator.hasNext())
System.out.println(iterator.next());
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Verkettete Listen
13-526
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Einführung in die Programmierung
mit Java
Teil 14: Hashtabellen und Suchbäume
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
26. Januar 2016
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-527
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Inhalt Teil 14: Hashtabellen und Suchbäume
57
Collection
Streams
58
Mengen
59
Abbildungen
60
Hashing
61
Binäre Suchbäume
62
Operationen in BST
63
Enum
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-528
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Datenstrukturen
Datenstrukturen legen fest, wie Daten im Speicher angeordnet
werden und nach welchem Muster darauf zugegriffen wird.
Modulares Design von Datenstrukturen:
Interface legt Funktionalität fest
Implementierung legt Effizienz fest
Austausch der Implementierung darf Performanz beeinflussen, nicht
jedoch semantische Korrektheit!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-529
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Datenstruktur Beispiele
Beispiele für beliebte Datenstrukturen in Java sind:
Struktur
Menge
Liste
Abbildung
Interface
Set<E>
List<E>
Map<K,V>
Implementierung
HashSet, TreeSet, EnumSet, . . .
ArrayList, LinkedList, Stack, . . .
HashMap, TreeMap, EnumMap, . . .
E repräsentiert jeweils den Typ der Elemente.
Implementierungen können ein Interface spezialisieren, z.B. durch
Forderung von Zusatzeigenschaften an Typ der verwalteten Daten:
EnumSet<E extends Enum<E>>
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-530
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Interface Collection
Das Interface Collection<E> erfasst alle Arten von (endlichen)
Sammlungen von Objekten eines Typs E.
Beispiel
Collection<Integer> bezeichnet Sammlungen von Zahlen;
LinkedList<Integer> ist auch eine Sammlung von Zahlen und ist
in der Tat auch ein Subtyp von Collection<Integer>.
Funktionalität
Test, ob Element enthalten ist
Hinzufügen / Entfernen eines Elementes
Abfrage der Anzahl der enthaltenen Elemente
Iteration über Elemente der Menge
Streamen der Elemente
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-531
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Interface Collection<E>
interface Collection<E> extends Iterable<E>
int
size();
boolean
isEmpty();
boolean
add(E e);
boolean
addAll(Collection<? extends E> c);
boolean
contains(Object o);
boolean
containsAll(Collection<?> c);
boolean
remove(Object o);
boolean
removeAll(Collection<?> c);
boolean
retainAll(Collection<?> c);
Stream<E>
stream();
Stream<E>
parallelStream();
Iterator<E> iterator();
Bemerkungen
Vergleiche benutzen Methode equals
Typ Object aus historischen Gründen,
aber z.B. bei remove/contains ohnehin harmlos
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-532
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Streams
Streams sind Folgen von Werten. Ein Stream hat drei Phasen:
1
Erzeugung
2
Mehrere Transformationen der Elemente
3
Aggregation
Stream-Berechnung kann seriell oder auch parallel erfolgen.
Beispiel:
List<Integer> list;
// ...erstellen der Liste
list.stream()
.map(x -> x*x)
.filter(x -> x%2 == 0)
.forEach(x ->
System.out.print(x+","));
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
//
//
//
//
Erzeugung
Transformation
Transformation
Aggregation
Hashtabellen und Suchbäume
14-533
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Notation
Beispiel
List<Integer> oddnums = Stream.iterate(1, n->n+1)
.filter(\x -> x%2 > 0)
.limit(27)
.collect(Collectors.toList());
Das man die Transformation jeweils in eine eigene Zeile schreibt, ist
eine nicht-bindende Konvention.
Hinweis:
Der Punkt . ist der gewöhnliche bekannte Methoden-Zugriff auf ein
Objekt. Die Verkettung mehrerer solcher Zugriffe funktioniert also
genau wie hier:
List<Integer> ilist;
String s = ilist.listIterator().next().toString();
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-534
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Notation
Beispiel
Stream<Integer> istr = Stream.iterate(1, n->n+1);
istr = istr.filter(\x -> x%2 > 0).limit(27);
List<Integer> ilst = istr.collect(Collectors.toList());
Das man die Transformation jeweils in eine eigene Zeile schreibt, ist
eine nicht-bindende Konvention.
Hinweis:
Der Punkt . ist der gewöhnliche bekannte Methoden-Zugriff auf ein
Objekt. Die Verkettung mehrerer solcher Zugriffe funktioniert also
genau wie hier:
List<Integer> ilist;
String s = ilist.listIterator()
.next()
.toString();
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-534
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Beispiele: Stream Erzeugung
Aus Collections: Mit den Collections<E>-Methoden
Stream<E> stream();
und
Stream<E> parallelStream();
Mit Generatoren:
Typ-spezifische Generatoren
IntStream.rangeClosed(11,33)
Unendliche Iteration
Stream.iterate(0, n -> n + 10)
Auflistung aller Werte
Stream.of(2,4,42,69,111)
Aus Dateien:
Files.lines(Paths.get("file.txt),
Charset.defaultCharset())
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-535
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Beispiele: Stream Transformationen
Stream<T> filter(Predicate<? super T> predicate)
Filtert Elemente aus dem Strom heraus
Stream<T> skip(long n)
Entfernt feste Anzahl Elemente aus dem Strom
Stream<T> distinct()
Entfernt doppelte Elemente aus dem Strom
Stream<T> sorted(Comparator<? super T> comparator)
Sortiert alle Elemente des Stroms
Stream<R> map(Function<? super T,? extends R> mapper)
Anwendung einer Funktion auf einzelne Elemente
IntStream mapToInt(ToIntFunction<? super T> mapper)
Konvertierung der Elemente nach Integer
Transformationen betrachten jedes Element für sich; es sollten nicht
über Seiteneffekte mehrere Elemente simultan beeinflusst werden!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-536
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Beispiele: Stream Aggregation
T reduce(T identity, BinaryOperator<T> accumulator)
Elemente werden durch binären Operator verknüpft
z.B. aufaddieren, aufmultiplizieren, etc.
sum(), average(), max(), min(), . . .
Vordefinierte Reduktionen
count()
Anzahl der verbleibenden Strom-Elemente zählen
void forEach(Consumer<? super T> action)
Einen Befehl für jedes Element ausführen
R collect(Collector<? super T,A,R> collector)
Wieder in einer Collection aufsammeln
Fazit Streams:
erlauben kurze Beschreibung komplexer Operationen
einfache Einführung paralleler Verarbeitung
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-537
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Endliche Mengen
Endliche Menge von verschiedenen Elementen gleichen Typs
Gleichheit muss verfügbar sein
Elemente maximal einmal enthalten
Reihenfolge unerheblich
sonst: Bag / MultiSet
sonst: LinkedHashSet
Funktionalität
Test, ob Element in Menge enthalten ist
Primäre Op.
Hinzufügen / Entfernen eines Elementes
Bildung von Vereinigungs- / Schnittmenge
Iteration über Elemente der Menge
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-538
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Interface Set<E>
interface Set<E> extends Collection<E>
boolean contains(Object o);
boolean add(E e);
boolean addAll(Collection<? extends E> c);
boolean remove(Object o);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
Iterator<E> iterator();
...
Nahezu gleich wie Collection, aber mit stärkeren Annahmen:
jedes Element-Objekt darf nur einmal vorhanden sein
Element-Objekte sollten möglichst immutable sein
Vergleiche benutzen Methode equals
Implementierung mittels Hashing oder Suchbäumen
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-539
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Endliche Mengen
Die grundlegende Operationen auf einer Menge sollten möglichst
schnell ablaufen:
Element hinzufügen
Element entfernen
Test auf Elementschaft
Die Schnittstelle Set<E> wird z.B. durch folgende Klassen
implementiert:
HashSet<E> Operationen add, remove, contains haben
höchstens linearen Aufwand, in der Praxis meist
jedoch konstant – hängt auch vom Hashing ab
siehe Abschnitt “Hashing”
TreeSet<E> Operationen add, remove, contains haben immer
logarithmischen Aufwand
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-540
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Anwendungsbeispiel
import java.util.*;
import javax.swing.JOptionPane;
public class SetTest
{
public static void main(String[] args) {
Set<String> names = new HashSet<String>();
boolean done = false;
while (!done) {
String input = JOptionPane.showInputDialog(
"Add Name, Cancel when done.");
if (input == null)
done = true;
else {
names.add(input);
print(names);}}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-541
Hashtabellen und Suchbäume
}
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
done = false;
while (!done) {
String input = JOptionPane.showInputDialog(
"Remove Name, Cancel when done.");
if (input == null)
done = true;
else {
names.remove(input);
print(names);
}
}
System.exit(0);
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-542
Hashtabellen und Suchbäume
}
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
private static void print(Set<String> s) {
Iterator<String> iter = s.iterator();
System.out.print("{");
while (iter.hasNext()) {
System.out.print(iter.next());
System.out.print(" ");
}
System.out.println("}");
}
Bemerkung
Außer beim Konstruktor verwenden wir die Schnittstelle Set<E>,
nicht die speziellere Klasse HashSet<E>.
Dies hat den Vorteil, dass wir nur eine Zeile abändern müssen, falls
wir auf die Implementierung TreeSet<E> umzusteigen wollen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-543
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Endliche Abbildungen
Einer Menge von Schlüssel-Objekten der Klasse K (engl. keys)
wird jeweils genau ein Werte-Objekt der Klasse V (engl. values)
zugeordnet.
Eine endliche Abbildung (finite map) kann als
zweispaltige Tabelle aufgefasst werden, wobei keine
zwei Zeilen den gleichen Schlüssel haben.
z.B. wie in einem Wörter- oder Telefonbuch
key
Martin
Sigrid
Steffen
value
9341
9337
9139
Funktionalität
Abfragen ob Schlüssel vorhanden ist
Abfragen des Wertes eines eingetragenen Schlüssels
Eintragen/Entfernen von Schlüssel/Wert Zuordnungen
Schlüssel bilden wieder eine endliche Menge!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-544
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Interface Map<K,V>
interface Map<K,V>
V
get(Object key);
// may return null
V
remove(Object key); // may return null
V
put(K key, V value); // may return null
boolean
containsKey(Object key);
boolean
containsValue(Object value);
Set<K>
keySet();
Collection<V> values();
...
Bildet Schlüssel-Objekte K auf Werte-Objekte V ab
Schlüssel-Objekte sollten möglichst immutable sein
Vergleiche benutzen equals
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
und oft auch hashCode
Hashtabellen und Suchbäume
14-545
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Implementierungen
von Abbildungen
Implementierung HashMap<K,V> durch eine Hashtabelle;
speichert Paare von Schlüsseln und Werten
Nachteile:
Effizienz hängt stark von hashCode ab.
Einfügen kann rehashing erfordern
Implementierung TreeMap<K,V> durch eine schnell
durchlaufbare Suchbäume
Nachteile:
Suchen, Einfügen und Löschen meist O (log n)
Einfügen oder Löschen kann Restrukturierung erfordern
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-546
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Anwendungsbeispiel
import java.util.*;
import javax.swing.JOptionPane;
import java.awt.Color;
public class MapTest {
public static void main(String[] args) {
Map<String,Color> favoriteColors =
new HashMap<String,Color>();
favoriteColors.put("Juliet", Color.pink);
favoriteColors.put("Romeo", Color.green);
favoriteColors.put("Adam", Color.blue);
favoriteColors.put("Eve", Color.pink);
print(favoriteColors);
favoriteColors.put("Adam", Color.yellow);
favoriteColors.remove("Romeo");
print(favoriteColors);
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-547
Hashtabellen und Suchbäume
}
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
private static void print(Map<String,Color> m) {
Set<String> keySet = m.keySet();
Iterator<String> iter = keySet.iterator();
while (iter.hasNext()) {
String key = iter.next();
Color value = m.get(key);
System.out.println(key + "->" + value);
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-548
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Implementierungsideen zu Set<E> und Map<K,V>
Als verkettete Liste
Modifiziere add-Methode so, dass keine Dubletten entstehen
bzw. keinem Schlüssel mehrere Werte zugewiesen werden.
Schlimmstenfalls muss die gesamte Liste durchlaufen werden.
Alternative: Ordnung auf E bzw. K ausnutzen.
; Suchbäume
Als Array mit Elementen vom Typ E bzw. Pair<K,V>
Gleiche Schwierigkeiten wie oben.
Array muss ggf. vergrößert werden.
Als “Array” mit Elementen vom Typ boolean bzw. V und
Indices vom Typ E bzw. K.
Indices können nur vom Typ int sein.
Idee: “Umrechnung” von E bzw. K auf int.
; Hashing
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-549
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Gleichheit von Objekten
Von Folie 3-111 kennen wir:
Pointer-Vergleich o1 == o2: Prüft, ob beide Referenzen auf
identische Speicheraddressen zeigen.
Vergleich durch o1.equals(o2): Semantische Gleichheit, ob
zwei Objekte “gleiche” Werte enthalten, ggf. aufwändig
Hashing
Berechnet einen “Fingerabdruck” eines Objekts.
Wenn die Berechnung des “Fingerabdrucks” schnell geht, kann man
damit Vergleiche abkürzen: Zwei Objekte mit verschiedenen
“Fingerabdruck” braucht man nicht mehr mit equals vergleichen!
Aber in seltenen Fällen können verschiedene Objekte den gleichen
Fingerabdruck haben.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-550
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Hashwerte
Die Klasse Object enthält eine Methode
int hashCode();
Sie liefert zu jedem Objekt einen Integer, den Hashcode oder
Hashwert.
Die Spezifikation von hashCode besagt, dass zwei im Sinne von
equals gleiche Objekte denselben Hashwert haben sollen.
Es ist aber erlaubt, dass zwei verschiedene Objekte denselben
Hashwert haben. Das ist kein Wunder, denn es gibt ja “nur”
232 int-Werte.
Allerdings sorgt eine gute Implementierung von hashCode dafür,
dass die Hashwerte möglichst breit gestreut (to hash = fein
hacken) werden. Bei “zufälliger” Wahl eines Objekts einer festen
Klasse sollen alle Hashwerte “gleich wahrscheinlich” sein.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-551
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Fallstricke Gleichheit / Hashing
Wenn man die Methode equals überschreibt, so muss man
auch immer Methode hashCode überschreiben!
Es muss gelten:
Wenn x.equals(y) dann x.hashCode() == y.hashCode()
Beim Überschreiben der Methode equals wird Spezifikation
der Gleichheit verletzt:
Reflexiv: x.equals(x) ist immer wahr
Symmetrisch: x.equals(y) == y.equals(x) gilt immer
Transitiv: x.equals(y) und y.equals(z)
impliziert x.equals(z)
Voraussetzung: x,y,z nicht null
Gelten diese Eigenschaften nicht, so funktionieren HashSet<E>,
HashMap<K,V>, etc. nicht mehr richtig!
Tipp: equals und hashCode durch IDE generieren lassen
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-552
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Implementierung von Set als Hashtabelle
Eine Möglichkeit, eine Menge zu implementieren, besteht darin, ein
Array s einer bestimmten Größe SIZE vorzusehen und ein Element
x im Fach x.hashCode() % SIZE abzulegen.
Das geht eine Weile gut, funktioniert aber nicht, wenn wir zwei
Elemente ablegen möchten, deren Hashwerte gleich modulo SIZE
sind. In diesem Falle liegt eine Kollision vor.
Um Kollisionen zu begegnen kann man in jedem Fach eine
verkettete Liste von Objekten vorsehen.
Für get und put muss man zunächst das Fach bestimmen und
dann die dort befindliche Liste linear durchsuchen.
Sind Kollisionen selten, so bleiben diese Listen recht kurz und der
Aufwand hält sich in Grenzen.
Genaue stochastische Analyse erfolgt in “Effiziente Algorithmen”.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-553
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Implementierung von Map als Hashtabelle
Praktisch dieselbe Datenstruktur kann man auch für Abbildungen
verwenden:
Die Bindung k 7→ x wird im Fach k.hashCode() % SIZE abgelegt.
Dadurch ist sichergestellt, dass zu jedem Schlüssel nur ein Eintrag
vorhanden ist.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-554
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Binäre Suchbäume
Sind die Einträge einer Menge (bzw. die Schlüssel einer map)
angeordnet, so können alternativ zu Hashtabellen binäre
Suchbäume eingesetzt werden.
Der Einfachheit halber arbeiten wir mit Einträgen vom Typ String.
Im allgemeinen nimmt man <E extends Comparable<E>> o.ä. Ein
binärer Baum besteht aus Objekten (“Knoten”) der folgenden
Klasse:
class Node {
String data;
Node left;
Node right;
}
Node(String data, Node left, Node right) {
this.data = data;
this.left = left;
this.right = right;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-555
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Grafische Darstellung
Das Objektdiagramm zu einem binären Baum sieht tatsächlich wie
ein Baum aus.
Man zeichne das Objektdiagramm zu
fr = new Node("Friedrich",null,null);
ma = new Node("Margarete",null,null);
to = new Node("Torsten",fr,ma);
sa = new Node("Sabine",null,null);
yannick = new Node("Yannick",to,sa);
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-556
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Strukturbedingung
Genauer gesagt liegt ein binärer Baum nur dann vor, wenn das
Objektdiagramm tatsächlich wie ein Baum aussieht.
kl = new Node("Klon",yannick,yannick);
ist kein binärer Baum.
Erst recht ist
om = new Node("Om",null,null);
om.left = om; om.right = om;
kein binärer Baum.
Wir verzichten auf die formale Definition.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-557
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Terminologie
Sei tree ein Baum
bzw. Variable eines Baum-Typen
Der Knoten, auf den tree zeigt, bezeichnet man als die
Wurzel des Baumes (der Baum selbst besteht ja aus der
Gesamtheit aller Knoten, nicht nur dem Wurzelknoten).
Die Bäume tree.left und tree.right heißen rechter und
linker Teilbaum von tree.
Alle Bäume, welche von irgendeinem Knoten von tree aus
erreichbar sind, heißen Teilbäume von tree.
Ein Knoten kn mit kn.left==null und kn.right==null
heißt Blatt.
Ein Knoten, der kein Blatt ist, heißt innerer Knoten.
Von der Wurzel gibt es zu jedem Blatt einen eindeutigen Pfad
durch Verfolgen der left und right Felder.
Die Länge des längsten Pfades heißt Höhe des Baumes.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-558
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Einträge
Zu einem binären Baum definiert man die Menge seiner Einträge
durch folgende Methode:
public static Set<String> entries(Node t) {
Set<String> result = new HashSet<String>();
if (t == null) return result;
else {
result.add(t.data);
result.addAll(entries(t.left));
result.addAll(entries(t.right));
return result;
So gilt etwa
entries(yannick) =
{"Yannick", "Thorsten", "Friedrich", "Margarete", "Sabine"}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-559
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Binärer Suchbaum
Ein binärer Baum t ist ein binärer Suchbaum (binary search tree,
BST), wenn folgendes gilt:
1
2
3
Entweder ist t gleich null, also leer,
Oder alle Elemente von entries(t.left) sind kleiner als
t.data und t.data ist kleiner als alle Elemente von
entries(t.right)
Und t.left und t.right sind selbst wieder binäre
Suchbäume.
Beispiele an der Tafel
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-560
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Suche in BST
Um festzustellen, ob ein gegebenes Element in einem binären
Suchbaum vorhanden ist, verwendet man folgende Methode:
public static boolean member(String x, Node t) {
if (t==null) return false;
else if (x.compareTo(t.data) == 0)
return true;
else if (x.compareTo(t.data) < 0) /* x<t.data */
return member(x, t.left);
else /* x.compareTo(t.data) > 0 */ /* x>t.data */
return member(x, t.right);
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-561
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Einfügen in BST
Um ein neues Element in einen binären Suchbaum einzufügen,
verwendet man folgende Methode:
public static Node insert(String x, Node t) {
if (t==null) return new Node(x,null,null);
else if (x.compareTo(t.data) == 0)
return t;
else if (x.compareTo(t.data) < 0) {
t.left = insert(x,t.left);return t;
}
else {
t.right = insert(x,t.right);return t;
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-562
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Erläuterung
Durch Vergleich mit dem Wurzeleintrag stellt man fest, ob das
neue Element im linken oder im rechten Teilbaum einzufügen
ist.
Man könnte versuchen, insert mit Rückgabetyp void zu
implementieren. Dann gibt es aber Probleme mit
insert(x,null);.
Anders gesagt, gibt insert(x,t) meistens wieder das Objekt
t selbst zurück, nachdem allerdings eines seiner “Kinder”
modifiziert wurde.
Im Falle insert(x,null) wird aber natürlich nicht null
zurückgegeben, sondern ein frischer Baum mit einem Eintrag.
Man könnte das Einfügen auch iterativ mit einer while
Schleife realisieren, die den entsprechenden Pfad des Baumes
abfährt.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-563
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Entfernen aus BST
Will man einen Eintrag x aus t entfernen, so gibt es vier Fälle:
x.compareTo(t.data) < 0: Man entferne x rekursiv aus
t.left;
x.compareTo(t.data) > 0: Man entferne x rekursiv aus
t.right;
x.compareTo(t.data) == 0: und t.right==null. Man
gebe t.left zurück.
x.compareTo(t.data) == 0: und t.right!=null. Man
überschreibe t.data mit dem nächstgrößeren Eintrag. Der
befindet sich ganz links in t.right.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-564
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Entfernen aus BST
public static Node remove(String x,Node t) {
if (t == null) return t;
else if (x.compareTo(t.data) == 0) {
if (t.right == null) return t.left;
else {
Node s = t.right;
if (s.left == null) {
t.data = s.data;
t.right = s.right;
} else {
while(s.left.left != null)
{ s = s.left; }
t.data = s.left.data;
s.left = s.left.right;
}
return t;
}
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-565
Hashtabellen und Suchbäume
}
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
else if (x.compareTo(t.data) < 0) {
t.left = remove(x,t.left); return t;
}
else {
t.right = remove(x,t.right); return t;
}
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-566
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Komplexität
Die Laufzeit der beschriebenen Operationen auf einem BST t ist
proportional zur Höhe von t. (Höhe = Länge des längsten Pfades
von der Wurzel zu einem Blatt.)
Unterscheiden sich die Höhe des linken und rechten Teilbaums um
höchstens Eins und gilt diese Eigenschaft auch für alle Teilbäume,
so ist der Baum balanciert. In diesem Fall ist die Höhe
proportional zum Logarithmus der Zahl der Einträge.
Geht die Balancierung durch eine Einfüge- oder Löschoperation
verloren, so kann man sie durch geeignete Rotationen
wiederherstellen.
In der Praxis führt man im BST zusätzliche Verwaltungsinformation
mit, die es erlaubt, eine drohende Verletzung der Balancierung
schnell zu erkennen. (AVL-Bäume, Rot-Schwarz-Bäume).
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-567
Collection Mengen Abbildungen
Links- und Rechtsrotation
Links- und Rechtsrotation
Hashtabellen und Suchbäume
Rechtsrotation
Node
Hashing Binäre Suchbäume BST-Ops Enum
Node
left
right
data = B
left
right
data = A
Linksrotation
Node
Node
left
right
data = A
r
t
left
right
data = B
r
s
s
t
Manbeachte,
beachte,dass
dassdie
dieRotationen
Rotationendie
dieBST-Eigenschaft
BST-Eigenschafterhalten.
erhalten.
Man
469
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-568
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Verwendung von BST
Mit BST lassen sich die Schnittstellen Set<E> und Map<E>
implementieren (Java’s TreeSet<E> und TreeMap<E>).
Bei geeigneter Balancierung garantieren BST eine schnelle
Zugriffszeit (Logarithmus der Größe). Bei Hashtabellen hängt die
Zugriffszeit von der Hashfunktion ab und der Verteilung der
Zugriffe ab.
Verwendet man konsequent die Schnittstellen Set und Map, so kann
man sehr leicht zwischen den beiden Implementierungen wechseln.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-569
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Zusammenfassung Hashtabellen und
Suchbäume
Mengen und Abbildungen sind als Schnittstellen Set und Map
repräsentiert und erlauben den Zugriff auf Daten ohne
Rücksicht auf die Reihenfolge.
Hashtabellen und binäre Suchbäume implementieren diese
Schnittstellen.
Binäre Suchbäume (BST) können verwendet werden, wenn die
Daten angeordnet sind.
In einem BST befinden sich links von einem Knoten jeweils
kleinere Einträge und rechts von einem Knoten jeweils größere
Einträge. Dadurch kann man sich bei der Suche jeweils auf
einen einzigen Pfad beschränken.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-570
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Endliche Aufzählungen
Endliche Aufzählungen (engl. Enumeration) bieten sich immer
dann an, wenn Menge der Optionen vor Kompilieren bekannt
Beispiele
Booleans: true, false
kein enum in Java
Wochentage: Montag, Dienstag, . . . , Sonntag
Noten: “Sehr gut”,. . . ,“Ungenügend”
Spielkarten: Kreuz, Pik, Herz, Karo
Nachrichtentypen eines Protokolls: login, logout, chat, . . .
Optionen, z.B. Kommandozeilenparameter
Aufzählungen dürfen sich mit Programmversion ändern
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-571
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Probleme ohne enum
Aufzählungen oft mit finalen int/String Konstanten realisiert,
dies hat aber Nachteile:
Keine Typsicherheit: int-Konstante MONTAG kann auch
dort verwendet werden, wo eine Spielkartenfarbe erwartet wird.
Keine Bereichsprüfung: Wert 42 kann übergeben
werden, wo eine Wochentag int Konstante erwartet wird.
Sprechende Namen nicht erzwungen:
“Hacks” mit direkten Zahlen können auftauchen
Geringe Effizienz: z.B. Vergleich von String Konstanten;
Keine Modularität: Gesamt-Rekompilation bei
Änderungen
Seit Java 1.5: enum für endliche Aufzählungen möglich!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-572
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
enum Deklarationen
Beispiel
public enum Kartenfarbe { KREUZ, PIK, HERZ, KARO; };
Definiert Typ Kartenfarbe mit 4 Konstanten.
mit Komma getrennt, mit Semikolon abgeschlossen
Verwendung durch Kartenfarbe.PIK
Kartenfarbe ist reguläre Java-Klasse:
Nur jeweils eine Instanz pro statischer Konstante,
d.h. es kann gefahrlos == verwendet werden
Verschiedene Enums können gleiche Konstanten haben:
Verwechslung wird durch Typsystem ausgeschlossen
Erbe von java.lang.Enum, Methoden automatisch erstellt
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-573
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
java.lang.Enum
Folegende Methoden werden über java.lang.Enum automatisch
für jedes enum bereitgestellt:
boolean equals(Object other)
Direkt verwendbar
int hashCode()
Direkt verwendbar
int compareTo(E o)
Vergleich gemäß Definitionsreihenfolge
String toString()
Umwandlung zur Anzeige
static <T extends Enum<T>> valueOf(String)
String name()
NICHT verwenden!
int ordinal()
NICHT verwenden!
Erlaubt optimierte Versionen EnumMap<K extends Enum<K>,V>
und EnumSet<E extends Enum<E>>
anstatt Bit-Felder immer EnumSet verwenden!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-574
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
values()
Weiterhin wird für jedes enum eine statische Methode values()
definiert, welche ein Array aller Konstanten liefert:
for (Kartenfarbe f : Kartenfarbe.values()) {
System.out.println(f);
}
Reihenfolge der Konstanten wie in der Deklaration des enums!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-575
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Attribute
Konstanten können mit anderen Werten assoziiert werden, welche
wie Attribute der enum-Klasse behandelt werden.
Dazu Konstruktor und getter-Methoden definieren
Konstruktoren müssen immer private sein
Auch beliebige andere Methoden sind erlaubt
public enum Feld {
FOREST("Wald",2), MEADOW("Wiese",2),
private final String typ;
private final int ertrag;
}
MOUNTAIN("Berg",1);
private Feld(String typ, int ertrag) {
this.typ = typ;
this.ertrag = ertrag;
}
public int ertrag() { return ertrag; }
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-576
Hashtabellen und Suchbäume
Collection Mengen Abbildungen Hashing Binäre Suchbäume BST-Ops Enum
Zusammenfassung Enum
Optionen können bei neuen Versionen leicht hinzugefügt
werden
enum Deklaration generiert Klasse mit statischen Instanzen
(kein öffentlicher Konstruktor)
Konstanten sind automatisch geordnet
Nützliche Methoden automatisch generiert, z.B. values()
EnumSet anstatt Bit-Felder verwenden
Werte über EnumMap oder Attribute assoziieren
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Hashtabellen und Suchbäume
14-577
Wiederholung
UML Vererbung Nebenläufigkeit Streams
Einführung in die Programmierung
mit Java
Teil 15: Wiederholung
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
3. Februar 2016
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Wiederholung
15-578
Wiederholung
UML Vererbung Nebenläufigkeit Streams
Inhalt Teil 15: Wiederholung
64
UML
65
Vererbung
66
Nebenläufigkeit
67
Streams
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Wiederholung
15-579
Wiederholung
UML Vererbung Nebenläufigkeit Streams
UML
UML-Diagramme werden primär als Strukturierungs- und
Modellierunsmittel dem Weg von der informellen Beschreibung zum
fertigen Programm eingesetzt.
Welche Komponenten gibt es?
Was leistet jede Komponente?
Wie interagieren die Komponenten?
Welche Abhängigkeiten gibt es?
Modellierung beinhaltet auch Abstraktion:
Wenn z.B. ein Konstruktor nicht aufgelistet wird, dann kann man
darauf schließen, dass dessen Verhalten gewöhnlich ist; ansonsten
gibt es wohl Besonderheiten zu beachten.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Wiederholung
15-580
Wiederholung
UML Vererbung Nebenläufigkeit Streams
UML Klassendiagramm
Jedes Objekt durch Box mit drei Teilen repräsentiert:
1
Klassenname
2
Attribute (=Instanzvariablen)
3
Methoden (meist nur public)
Pfeile
B von Unterklasse zu Oberklasse
−−−B von Klasse zu implementierte Schnittstelle
−−−→ “benutzt”-Beziehung, z.B. Aufruf von Methoden
Aggregation: “hat-ein” als Instanzvariable
Komposition: “ist-Teil-von”, bedeutet meist das
Lebensspanne durch Element an Spitze bestimmt wird, z.B.
Glied
MyLinkedList
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Wiederholung
15-581
Wiederholung
UML Vererbung Nebenläufigkeit Streams
Beispiel: UML Klassendiagramm I
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Wiederholung
15-582
Wiederholung
UML Vererbung Nebenläufigkeit Streams
Beispiel: UML Klassendiagramm II
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Wiederholung
15-583
Wiederholung
UML Vererbung Nebenläufigkeit Streams
UML Speicherdiagramm
Diagramm zeigt alle vorhandenen Objekte im Speicher,
und meistens noch alle lokale Variablen mit Inhalt.
Jedes Objekt durch Box mit zwei Teilen repräsentiert:
1
2
Klassenname
Falls lokale Variablen nicht separat angegeben
werden, wird im Boxtitel oft der lokale Bezeichner dem
Klassennamen mit : vorangestellt.
Attribute (=Instanzvariablen) mit aktuellem Inhalt
Nur eine Sorte Pfeile
I für Referenzen, also Verweise auf andere Objekte
. . . wird in ähnlicher Form auch als Objektdiagramm bezeichnet.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Wiederholung
15-584
Wiederholung
UML Vererbung Nebenläufigkeit Streams
Beispiel: UML Speicherdiagramm I
Speicher
Lokale Variablen
kerzen =
rotekerze =
grünekerze=
baum =
deko =
Weihnachtsbaum
lichterkette =
kugeln
=7
ArrayList<Kerze>
ArrayList<Kerze>
[0] =
[1] =
Kerze
brennt=true
Martin Hofmann, Steffen Jost
[0] =
[1] =
[2] =
Kerze
Kerze
brennt=false
brennt=false
Einführung in die Programmierung
Wiederholung
15-585
Wiederholung
UML Vererbung Nebenläufigkeit Streams
Beispiel: UML Speicherdiagramm II
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Wiederholung
15-586
Wiederholung
UML Vererbung Nebenläufigkeit Streams
Zusammenfassung Vererbung
Private Instanzvariablen sind weiter vorhanden, aber
unsichtbar!
Konsequenz: Zugriff auf geerbte Instanzvariablen nur über
geerbte Methoden
Alle geerbten Methoden weiterhin unverändert sichtbar.
Geerbte Methoden können überschrieben werden.
Überschriebene Methoden ersetzen ursprüngliche Definition
überall, auch wenn das Objekt über Subtyping als Ahne
aufgefasst wird.
Falle: Ohne Override kann es zu versehentlichen Überladen
kommen – insbesondere ist es es kein Überschreiben, wenn
Argumente spezialisiert werden!
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Wiederholung
15-587
Wiederholung
UML Vererbung Nebenläufigkeit Streams
Aufgabe Vererbung
Welche Ausgabe wird erzeugt?
A a = new A(3);
B b = new B(4);
a.ausgabe(a);
a.ausgabe(b);
b.ausgabe(a);
b.ausgabe(b);
public class A {
public int x;
public A(int x) { this.x=x; }
public int getX() { return x;}
public void ausgabe(A a) {
System.out.println(
"A "+ x + " "+ a.getX());
} }
public class B extends A {
public B(int x) { super(x); }
public int getX() {
return (3 * super.getX());
} }
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Wiederholung
15-588
Wiederholung
UML Vererbung Nebenläufigkeit Streams
Aufgabe Vererbung
Welche Ausgabe wird erzeugt?
A a = new A(3);
B b = new B(4);
a.ausgabe(a);
a.ausgabe(b);
b.ausgabe(a);
b.ausgabe(b);
Antwort:
A
A
A
A
3 3
3 12
4 3
4 12
Martin Hofmann, Steffen Jost
public class A {
public int x;
public A(int x) { this.x=x; }
public int getX() { return x;}
public void ausgabe(A a) {
System.out.println(
"A "+ x + " "+ a.getX());
} }
public class B extends A {
public B(int x) { super(x); }
public int getX() {
return (3 * super.getX());
} }
Einführung in die Programmierung
Wiederholung
15-588
Wiederholung
UML Vererbung Nebenläufigkeit Streams
Nebenläufigkeit
Grundsätzliche Fragestellung für Synchronisation:
Was passiert, wenn ich hier unterbrochen werde und sich ggf.
Instanzvariablen ändern?
⇒ Meist ein Problem, wenn Instanzvariablen gelesen, verändert und
zurückgeschrieben werden; insbesondere bei mehreren
Instanzvariablen.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Wiederholung
15-589
Wiederholung
UML Vererbung Nebenläufigkeit Streams
Probleme bei Nebenläufigkeit
Bei nebenläufigen Berechnungen mit mehreren Threads können u.a.
folgende Probleme auftreten:
Race-Condition Das Ergebnis der Berechnung hängt von der
Verzahnung der Threads ab.
Deadlock Ein Thread wartet auf das Ergebnis eines anderen
Threads, welche direkt oder indirekt selbst auf das Ergebnis des
ersten Threads wartet. Beide warten aufeinander, die
Berechnung kommt somit zum erliegen.
Durch den Einsatz von Synchronisation/Locks kann man
Race-Conditions vermeiden, erhöht aber prinzipiell die Gefahr von
Deadlocks.
Verschiedene Threads können sich gegenseitig beeinflussen.
Manchmal wird ein Thread schneller als ein anderer abgehandelt.
Da die Möglichkeiten der Verzahnung immens sind, ist das
Gesamtergebnis der Berechnung kaum vorhersagbar/testbar.
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Wiederholung
15-590
Wiederholung
UML Vererbung Nebenläufigkeit Streams
Beispiel: H11-1
Aufgabe H11-1 könnte man mit Streams direkt und ohne
Hilfsdefinition einfacher lösen:
List<Boolean> result =
xs.stream()
.map(x -> 3*x)
.map(x -> x%2==0)
.collect(Collectors.toList());
System.out.println(result.toString());
Der “Punkt” ist der gewöhnliche Methoden-Zugriff, d.h. man kann
ganz genauso schreiben:
System.out.println(xs.stream().map(x -> 4*x)
.collect(Collectors.toList()));
xs.stream().map(x -> x%2==0).forEach(
y -> System.out.print(y+", "));
Martin Hofmann, Steffen Jost
Einführung in die Programmierung
Wiederholung
15-591
Herunterladen