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