# Fortgeschrittene Programmierung mit Java - Tutorium &copy; By Furat Hamdan ### Allgemeine Informationen - Im Rahmen der Lehrveranstaltung Fortgeschrittene Programmierung mit Java/Programmieren II werden 6 Übungsblätter mit insgesamt 120 Punkten ausgegeben. - Insgesamt können Sie durch das korrekte Lösen der Hausaufgaben maximal 120 Punkte erreichen. - Sie müssen mindestens 90 Punkte erreichen, um das Hausaufgabenkriterium zu erlangen. Dieses benötigen Sie, um an der schriftlichen Prüfung am Ende des Semesters teilnehmenzu dürfen. - Für jede Hausaufgabe haben Sie zwei Wochen Zeit zur Bearbeitung. Laden sie ihre Lösung bis zu dieser Frist auf ISIS hoch. - In den Tutorien werden gemeinsam mit dem Tutor Tutoriumsaufgaben bearbeitet die den vermittelten Stoff der Vorlesung vertiefen sollen. - Sowohl für die Hausaufgaben, sowie für die Tutoriumsaufgaben werden Musterlösungen angeboten. ### Notwendige Software - [IntelliJ IDEA](https://www.jetbrains.com/idea/) - [Java 8](https://www.java.com/de/download/help/download_options.html) - [Das Build-Tool Apache Maven](http://maven.apache.org/install.html) - [Scala Plug-in](https://plugins.jetbrains.com/plugin/1347-scala) - [Anaconda (Jupyter Notebook)](https://www.anaconda.com/products/individual) - [Scala Kernel](https://almond.sh/docs/quick-start-install) #### Empfehlung für macOS [Der Paketmanager Homebrew](https://brew.sh/index_de) #### Java 8 Installation > $ brew install --cask adoptopenjdk/openjdk/adoptopenjdk8 #### Apache Maven Installation - [macOS](https://mkyong.com/maven/install-maven-on-mac-osx/) - [Windows](https://howtodoinjava.com/maven/how-to-install-maven-on-windows/) #### Scala-Plugin Installation - [macOs und Windows](https://docs.scala-lang.org/getting-started/intellij-track/getting-started-with-scala-in-intellij.html) #### Scala REPL Installation > $ brew install scala #### Jupyter Notebook Installation > $ brew install jupyter #### Jupyter Notebook starten > $ jupyter notebook ## Tutorium #1 ### Prozesse vs. Threads #### Prozesse * Ausführung eines Programms in seiner Prozessumgebung * **Prozessumgebung (Ablaufumgebung, Ausführungsumgebung)** besteht aus dem Prozesskontext und zugeordneten Ressourcen * **Ressourcen (Betriebsmittel):** CPU, Arbeitsspeicher, offene Dateien, Konsolen, Netzverbindungen, etc. * **Prozesskontext:** Zustand des Adressraumes, Registerinhalte, Beschreibung der zugeordneten Ressourcen #### Threads * Parallel ablaufende Aktivitäten in einem Prozess * Synonyme: Fäden, Ausführungsstränge, Leichtgewichtprozesse * Rechenarbeit eines Prozesses wird auf mehrere Aktivitäten verteilt, welche nebenläufig oder parallel ablaufen können #### Eigenschaften von Prozessen * Jeder Prozess besitzt seinen eigenen Adressraum (virtueller Speicher), welcher ihn von anderen Prozessen im System trennt * Jeder Prozess enthält mindestens einen Thread und kann beliebig viele Threads generieren * Jedem Prozess sind die von ihm genutzten Ressourcen zugeordnet #### Eigenschaften von Threads * Jeder Thread gehört zu einem bestimmten Prozess * Threads besitzen Zustände (aktiv, rechenbereit, blockiert) * Threads besitzen eigene Kontrollblöcke und Stacks * Alle Threads eines Prozesses haben eine gemeinsame Prozessumgebung * Threads sind gegenüber anderen Threads desselben Prozesses nicht geschützt <img src="https://i.imgur.com/fRgBXYX.png" width="350" height="500"> ### Thread-Lebenszyklus <img src="https://i.imgur.com/QtSymDw.png" width="500" height="375"> #### Zustände * **NEW**: Thread wurde erzeugt, aber noch nicht gestartet * **RUNNABLE**: Thread ist lauffähig * **READY**: Thread könnte ausgeführt werden, wurde aber noch keiner CPU-Ressource (Core) zugewiesen * **RUNNING**: Thread befindet sich in Ausführung * **TIMED WAITING**: Thread wird nicht ausgeführt, da er für eine bestimmte Zeit suspendiert wurde * **WAITING**: Thread wird nicht ausgeführt, da er für unbestimmte Zeit suspendiert wurde * **BLOCKED**: Thread wird nicht ausgeführt, da er auf Ressourcen wartet * **TERMINATED**: Ausführung ist beendet ### Ableiten der Klasse Thread (extends Thread) ```java public class MyThread extends Thread { /* public MyThread(){ start(); } */ public void run() { System.out.println("Hallo Welt"); } public static void main(String[] args){ MyThread t = new MyThread(); t.start(); } } ``` ### Implementieren der Schnittstelle Runnable (implements Runnable) ```java public class Thread { public Thread() {...} public Thread(Runnable r) {...} ... } ``` ```java public interface Runnable { public void run(); } ``` ```java public class SomethingToRun implements Runnable { public void run() { System.out.println("Hallo Welt"); } public static void main(String[] args){ SomethingToRun runner = new SomethingToRun(); // Runnable runner = new SomethingToRun(); Thread t = new Thread(runner); t.start(); } } ``` ### Implements Runnable vs. Extends Thread | Implements Runnable | Extends Thread | | ------------------- | -------------- | | Kann von einer Superklasse erben |Kann von keiner weiteren Superkasse erben| | Zugriff auf Thread-Methoden nur über Thread.currentThread() möglich | Zugriff auf Thread-Methoden direkt möglich | ## T#2 ### Variablen-Typen in Java **Lokale Variablen** * Variablen die innerhalb von Methoden deklariert werden, Methodenparameter, Exception-Handler * Gültigkeit beschränkt sich auf die Methode bzw. den Block innerhalb der Methode in dem sie deklariert wurde * Lebenszyklus endet mit der Terminierung der Methode bzw. des umgebenden Blockes * Speicherung im Stack des ausführenden Thread **Instanzvariablen** * Nicht-statische Variablen die der Instanz einer Klasse zugeordnet sind (Attribute) * Gültigkeit erstreckt sich auf die gesamte Instanz (wenn mit private gekennzeichnet), solange sie nicht von gleichnamigen lokalen Variablen verdeckt werden * Gültigkeit erstreckt sich auf alle umgebenden Objekte wenn mit public gekennzeichnet * Lebenszyklus beginnt mit der Instanziierung des Objektes und endet bei der Beseitigung des Objektes durch die Garbage Collection * Speicherung im Heap **Klassenvariablen** * Variablen die der Klasse zugehörig sind, nicht einzelnen Instanzen * Gültigkeit wie bei Instanzvariablen * Lebenszyklus beginnt mit dem Laden der Klasse in den Speicher und endet mit der Entfernung aus dem Speicher * Speicherung im Heap (genauer: in der Method Area, einem Teil des Heap) **Primitive Variablen** * Der für eine primitive Variable reservierte Speicherbereich enthält direkt den primitiven Wert, welcher der Variable zugeordnet werden soll * Primitive Datentypen: boolean, char, byte, short, int, long, float, double **Referenzvariablen** * Der für eine Referenzvariable reservierte Speicherbereich enthält eine Referenz (Adresse) * Referenz verweist auf den Speicherbereich im Heap, in dem das Objekt gespeichert ist ```java= public class myClass { public int x,y; //Instanzvariable (primitiver Datentyp) public String name; //Instanzvariable (Referenztyp) public static double pi = 3.14159; //Klassenvariable (primitiver Datentyp) public static PrintStream out; //Klassenvariable (Referenztyp) public void myMethod(int x){ int y = 20; //Lokale Variable (primitiver Datentyp) String name = "Test"; //Lokale Variable (Referenztyp) for (int i = 0; i < 50; i++){ //Lokale Variablen (primitiver Datentyp) long v; // Lokale Variable (primitiver Datentyp) } } } ``` ### Heap und Stack **Stack (Stapel, Stapelspeicher, Keller, Kellerspeicher):** * einem **Thread zugewiesener Speicherbereich** zur Ablage von lokalen Variablen und Zwischenergebnissen nach dem LIFO-Prinzip (Last-In-First-Out) * Sehr effizientes Ablegen und Lesen von Elementen * Wächst und schrumpft mit dem Programmverlauf * Kein explizites Freigeben des Speichers oder Garbage Collection notwendig **Heap (Halde, Haldenspeicher):** * einem **Prozess zugewiesener** und **allen Threads des Prozesses** zur Verfügung stehender Speicherbereich zur Ablage von Objekten, Klassen- und Instanzvariablen * Referenzierung von Elementen durch Zeiger * Ablegen und Lesen von Elementen ist langsam * Speicher muss explizit freigegeben werden (wenn kein Garbage Collection vorhanden) ![](https://i.imgur.com/Vh82zVv.png) ![](https://i.imgur.com/CRgWut6.png) ![](https://i.imgur.com/27fPEjm.png) ![](https://i.imgur.com/Pw6WBeJ.png) ### Scheduling * Scheduler (Zeitablaufsteuerung) legt fest, welche Threads oder Prozesse wann und wie viel Prozessorzeit erhalten * Herausforderungen wenn mehr Threads abgearbeitet werden müssen als CPUs bzw. CPU-Kerne vorhanden sind * Spezifikation der JVM legt lediglich Richtlinien für das Scheduling fest * Unterschiedliche Auslegung dieser Richtlinien bei verschiedenen JVM-Implementierungen * JVMs delegieren Thread Scheduling an den Scheduler des jeweiligen Betriebssystems <img src="https://i.imgur.com/F6SYmIQ.png" width="450" height="350"> #### Scheduling-Strategien **Prioritätsscheduling** * Prioritätsscheduling ordnet jedem Thread eine Priorität zu, und die Abarbeitung der Threads folgt in der Reihenfolge ihrer Prioritäten **Präemptives Scheduling** * Präemptives (unterbrechendes) Scheduling unterbricht einen Thread, wenn ein anderer Thread mit höherer Priorität bereit (READY) ist * Ausführung eines Thread wird nicht unterbrochen, wenn Threads mit gleicher Priorität bereit sind (siehe Time Slicing) * Alternative: *kooperatives Scheduling* unterbricht die Abarbeitung beim Eintreffen höher priorisierter Threads nicht **(keine Verwendung in der JVM)** **FIFO** * FIFO (First-In-First-Out) ist eine Abarbeitungsreihenfolge für mehrere Threads mit gleicher Priorität: der am längsten wartende Thread kommt zuerst zur Ausführung Time Slicing * Time Slicing (Zeitscheibenverfahren) teilt einem Thread CPURessourcen für ein maximales Zeitintervall zu, bevor Zuteilung an einen anderen Thread übergeben wird * Nicht durch die JVM spezifiziert, aber von allen gängigen Betriebssystemen angewendet ## T#3 ### Thread-Sicherheit **Thread-Sicherheit** * Thread-Sicherheit (Thread Safety) garantiert, dass Teile eines Programms, die nebenläufig durch mehrere Threads ausgeführt werden, sich korrekt verhalten * Scheduling und Interleaving der Threads haben keinen Einfluss auf die korrekte Ausführung des Programms **Race Condition** * Race Condition (Wettlaufsituation oder kritischer Wettlauf): * Ergebnis einer Operation hängt von den Zeitpunkten und der Reihenfolge bestimmter Ereignisse ab * Spezialfall Data Race: mehrere parallel ausgeführte Threads geraten durch den Zugriff auf eine gemeinsame Variable in eine ungewollte zeitliche Abhängigkeit * Arten von Race Conditions * Read-Modify-Write * Check-Then-Act <img src="https://i.imgur.com/HAIbsgH.png" width="450" height="350"> <br> <br> | Thread 1 | Thread 2 | | Integer value | | -------- | -------- | ---- | ------------- | | | | | 100 | |read value| | ← | 100 | |add 3 | | | 100 | | write back| |→ | 103 | | | read value| ← | 103 | | | add 5 | | 103 | | | write back| → |108 | Thread 1 | Thread 2 | | Integer value | | -------- | -------- | ---- | ------------- | | | | | 100 | |read value| | ← | 100 | | |read value| ← | 100 | |add 3 | | | 100 | | | add 5 | | 100 | |write back| | | 103 | | | write back| → |105 **Read-Modify-Write** * Read-Modify-Write-Problem: Nebenläufige Threads ändern den Wert einer gemeinsamen Variablen basierend auf dem vorherigen Wert dieser Variablen <img src="https://i.imgur.com/IJLrLUo.png" width="400" height="110"> **Check-Then-Act** * Check-Then-Act-Problem: Nebenläufige Threads prüfen eine Bedingung die eine gemeinsame Variable enthält und, je nach Ergebnis der Prüfung, ändern dann den Wert dieser Variablen <img src="https://i.imgur.com/6lsEmDd.png" width="400" height="110"> **Kritischer Bereich** * Ein kritischer Bereich ist eine gekennzeichnete Ansammlung von Anweisungen zum Zwecke der Ablaufsteuerung mehrerer Threads. * Zwischenergebnisse der Einzelanweisungen (z.B. auf gemeinsam genutzten Variablen) können inkonsistente Zustände darstellen, auf welche andere Threads keinen Zugriff erhalten dürfen. * Das Ergebnis eines kritischen Bereichs darf nur als unteilbare (atomic) Einheit nach außen sichtbar werden. ### Monitore * Ein Monitor ist ein programmiersprachliches Konstrukt zur Kennzeichnung von kritischen Bereichen, in denen der Compiler bei der Übersetzung Synchronisationsmechanismen für * den wechselseitigen Ausschluss und * die Kooperation zwischen Threads einfügt. * Kritische Bereiche werden beim Java-Monitor mit dem Schlüsselwort synchronized gekennzeichnet * Mit synchronized können ganze Methoden oder Blöcke innerhalb von Methoden geschützt werden * Monitor wird bereitgestellt durch die Klasse Object, die den Wurzelkonten der Java-Vererbungshierarchie bildet, d.h. jede Klasse erbt von Object und verfügt daher automatisch über einen Monitor **Synchronized** | Instanzmethoden | Klassenmethoden | | -------------------------------------------------------------------- | -------------------------------------------------------------------- | | <img src="https://i.imgur.com/xUeJm80.png" width="270" height="100"> | <img src="https://i.imgur.com/Dns7ptr.png" width="270" height="100"> | | <img src="https://i.imgur.com/wCT7D7z.png" width="270" height="140"> | <img src="https://i.imgur.com/Y3QI240.png" width="270" height="140"> | | <img src="https://i.imgur.com/xyAL5EP.png" width="270" height="140"> | <img src="https://i.imgur.com/jVdhDzp.png" width="270" height="140"> | ### What Objects to Synchronize On Source: [Java Synchronized Blocks - Tutorials Jenkov](https:// "https://jenkov.com/tutorials/java-concurrency/synchronized.html") As mentioned several times in this Java synchronized tutorial, a synchronized block must be synchronized on some object. You can actually choose any object to synchronize on, but it is recommended that you :::info do not synchronize on String objects, or any primitive type wrapper objects ::: as the compiler might optimize those, so that you are using the same instances in different places in your code where you thought you were using different instance. Look at this example: ``` synchronized("Hey") { //do something in here. } ``` If you have more than one synchronized block that is synchronized on the literal **String** value "Hey", then the compiler might actually use the same String object behind the scenes. The result being, that both of these two synchronized blocks are then synchronized on the same object. That might not be the behaviour you were looking for. The same can be true for using **primitive type wrapper objects**. Look at this example: ``` synchronized(Integer.valueOf(1)) { //do something in here. } ``` If you call Integer.valueOf(1) multiple times, it might actually return the same wrapper object instance for the same input parameter values. That means, that if you are synchronizing multiple blocks on the same primitive wrapper object (e.g. use Integer.valueOf(1) multiple times as monitor object), then you risk that those synchronized blocks all get synchronized on the same object. That might also not be the behaviour you were looking for. To be on the safe side, synchronize on this - or on a new Object() . Those are not cached or reused internally by the Java compiler, Java VM or Java libraries. ### Erweiterung der Monitore um Warteräume <img src="https://i.imgur.com/qMzFreb.png" width="350" height="400" > * wait(): der aufrufende Thread wird blockiert und in den Warteraum geschickt, ein anderer Thread kann den kritischen Bereich betreten * notify(): der aufrufende Thread signalisiert einem blockierten Thread im Warteraum, dass er entsperrt werden darf (keine Auswirkung, wenn kein Thread im Warteraum wartet) ### Erzeuger/Verbraucher-Problem <img src="https://i.imgur.com/xvg0ppN.png" width="310" height="200"> <img src="https://i.imgur.com/VdIvL6N.png" width="460" height="200"> <img src="https://i.imgur.com/kx8MhGp.png" width="460" height="350"> <img src="https://i.imgur.com/pHIFwdN.png" width="460" height="350"> #### Does the notifyAll() method really wake up all the threads? [->link](https://"https://howtodoinjava.com/java/multi-threading/wait-notify-and-notifyall-methods/") Yes and no. All of the waiting threads wake up, but they still have to reacquire the object lock. So the threads do not run in parallel: they must each wait for the object lock to be freed. Thus, only one thread can run at a time, and only after the thread that called the notifyAll() method releases its lock. #### Why would you want to wake up all of the threads if only one is going to execute at all? There are a few reasons. For example, there might be more than one condition to wait for. Since we cannot control which thread gets the notification, it is entirely possible that a notification wakes up a thread that is waiting for an entirely different condition. [! Warum kann notify() zu Deadlocks führen, explained by stackoverflow](https://"https://stackoverflow.com/questions/37026/java-notify-vs-notifyall-all-over-again") ### Semaphore * Ein Semaphor S ist eine ganzzahlige Zählvariable, welche mittels zweier Operationen acquire() (zum Dekrementieren) und release() (zum Inkrementieren) verändert werden kann. * Der Anfangswert der Zählvariable gibt die Anzahl der Threads an, die sich gleichzeitig in einem durch den Semaphor geschützten kritischen Bereich aufhalten dürfen. * Jede acquire()-Operation überprüft, ob ein Eintreten in den kritischen Bereich möglich ist. * S>0: der Thread kann in den kritischen Bereich eintreten und S wird dekrementiert. * S=0: es befindet sich bereits die zulässige Anzahl von Threads im kritischen Bereich und der aufrufende Thread muss in einer Warteschlange warten. * Jede release()-Operation inkrementiert die Zählvariable und der erste wartende Thread (falls existent) kann in den kritischen Bereich eintreten, woraufhin S erneut dekrementiert wird. <img src="https://i.imgur.com/9VMcnRt.png" width="370" height="170"> **Semaphore in Java** * Semaphore werden in Java durch die Klasse Semaphore unterstützt * Zahlreiche Methoden der mit dem Semaphor verbundenen Zählvariable zum Verändern der Zählvariable * Semaphore(permits : int): Konstruktor mit Anfangswert der Zählvariablen * Semaphore(permits: int, fair:boolean): Wenn fair auf true gesetzt wird, dann wird garantiert, dass der Thread der am längsten auf den Lock wartet den Zugriff erhält. (FIFO-Prinzip) * acquire(): Dekrementieren beim Eintritt in den kritischen Bereich * release(): Inkrementieren beim Austritt aus dem kritischen Bereich * acquire(permits): Vermindern der Zählvariable um einen Wert permits * release(permits): analog * Binäre Semaphore lassen nicht mehr als einen Thread in den kritischen Bereich, d.h. der Anfangswert der Zählvariablen S beträgt 1. <img src="https://i.imgur.com/obzWP3b.png" width="370" height="280"> ## T#4 Scala basics Interessante Links * [Warum Scala](https://www.interviewbit.com/scala-interview-questions/) * [How tech giants use scala](https://sysgears.com/articles/how-tech-giants-use-scala/) ### Konstanten vs. Variablen * **Konstante/konstante Referenz:** Unveränderliche (immutable) Variable eines bestimmten Datentyps, d.h. ein Wert kann initial zugewiesen, die Zuweisung aber nicht verändert werden **Java:** ```java= final int x = 5; ``` **Scala:** ```scala= val x:Int = 5 ``` * **Variable/variable Referenz:** Veränderliche (mutable) Variable eines bestimmten Datentyps, d.h. ein Wert kann jederzeit zugewiesen, verändert, gelesen und gelöscht werden **Java:** ```java= int x = 5; ``` **Scala:** ```scala= var x:Int = 5 ``` ### Scala-Typenhierarchie <img src="https://i.imgur.com/w2Wh0n3.png" width="370" height="320"> * AnyVal: Wertdatentypen * AnyRef: Referenzdatentypen * Keine primitiven Datentypen wie in Java, aber interne Abbildung der Wertdatentypen auf primitive Datentypen der JVM **Nothing** * Nothing ist ein Untertyp von allen anderen Typen in Scala und hat keine Instanzen * Dient als Hilfe für Typableitung und Verifikationszwecke und kann alle Datentypen ersetzen * Kann als Rückgabewert einer Methode dienen, die niemals normal terminiert und immer eine Exception auslöst * Dient als Datentyp für nichtvorhandene Elemente in leeren Listen: List[Nothing] **Null und null** * Null ist ein Untertyp von allen Klassen unterhalb AnyRef * Einzige Instanz von Null ist null * Ersetzt das Keyword null in Java durch einen Wert null vom Typ Null und dient fast ausschließlich der Kompatibilität mit Java-Bibliotheken * Sollte in der reinen Scala-Programmierung nicht verwendet werden (besser: Option/Some/None, siehe später) ### Tuples * Ein Tuple ist ein geordneter Container für zwei oder mehr Werte, die unterschiedliche Datentypen haben können * Neu in Scala, d.h. kein Gegenstück in Java * Nützlich zur Gruppierung zusammengehöriger Werte * Die n Elemente eines Tuples sind mit _1,...,_n indiziert * Repräsentation von Key-Value-Pairs: Tuple mit zwei Elementen können durch den Relationsoperator (->) definiert werden ```scala= val person = ("Hans", 35, true) val name = person._1 val red = "red"->"0xff0000" // (red,0xff0000) ``` ### Match-Ausdrücke * Alternative zu if...else-Ausdrücken * Ein Ausdruck wird mit mehreren Mustern (Patterns) verglichen * Der Ausdruck hinter dem Muster mit der ersten Übereinstimmung wird ausgeführt * Entspricht der switch-Anweisung in Java * Unterschied: Abbruch nach dem ersten Muster mit Übereinstimmung, Ausführung mehrerer Ausdrücke bei Übereinstimmung mit mehreren Mustern nicht möglich ```scala= val x=10; val y=20 val max = x>y match { case true=>x case false=>y } ``` ### Ranges * to, by und until sind auf Integer-Werten definierte Methoden zur Erzeugung von Ranges, d.h. keine Operatoren * to erzeugt einen inklusiven Range bezüglich der Obergrenze * until erzeugt einen exklusiven Range bezüglich der Obergrenze * by gibt den Abstand der Werte an (optional, Default-Wert ist 1) ```scala= 1 to 10 //res0: Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 1.to(10) //res1: Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) (1 until 10 by 2).toList //res3: List[Int] = List(1, 3, 5, 7, 9) ('a' to 'z').toList //res74: List[Char] = List('a','b','c','d','e','f',...) ``` ### for-Notation * for-Notation berechnet für einen Eingabebereich und eine gebundene Variable, die durch den Eingabebereich iteriert, einen Ausdruck * Eingabebereich ist normalerweise durch eine Collection gegeben, z.B. Range oder List * Die gebundene Variable wird mit dem Schlüsselwort <- vom Eingabebereich getrennt * Der Ausdruck ist in der for-Notation optional und wird mit dem Schlüsselwort yield gekennzeichnet (3) * Die for-Notation ist in Scala keine for-Schleife, da sie intern über Collections iteriert und darauf anwendet * Die for-Notation wird oftmals auch als for-Comprehension bezeichnet ```scala= for (x <- 1 to 7){println(s"Day: $x")} //Day: 1 Day: 2 Day: 3 for (x <- 1 to 7) yield {s"Day: $x"} // res8: IndexedSeq[String] = Vector("Day: 1", "Day: 2", "Day: 3", ..) //Iterator Guards verbinden in einer for-Schleife den Iterator mit einem if-Ausdruck, um bestimmte Iterationen zu filtern val threes=for (i <- 1 to 20 if i%3==0) yield i // threes: collection.immutable.IndexedSeq[Int] = Vector(3, 6, 9, //Eingebettete Iteratoren (Nested Iterators) verhalten sich wie ineinander geschachtelte for-Schleifen for (x <- 1 to 2) { for (y <- 1 to 3) { print(s"($x, $y)") } } // (1, 1)(1, 2)(1, 3)(2, 1)(2, 2)(2, 3) for (x <- 1 to 2; y <- 1 to 3) { print(s"($x, $y)") } // (1, 1)(1, 2)(1, 3)(2, 1)(2, 2)(2, 3) ``` ### While und Do-While-Schleifen ```scala= var x=10; while (x>0) x-=1 val x=0 do println(s"Here I am, x=$x") while (x>0) // Here I am, x=0 ``` ### Methoden- und Funktionsdefinition ```scala= def name : (type1, type2) => rückgabetyp = (param1:type1, param2:type2) => {} // Deklarative Funktion --> Besser val oder var benutzen def name = (param1:type1, param2:typ2) => {} // Anonyme Funktion --> Besser val oder var benutzen def name (param1:type1, param2:type2) : rückgabetype = {} // Methode // val x:Int = 5 ``` [Warum kann man die main-Methode in einer Klasse nciht ausführen?](https://stackoverflow.com/questions/23416536/main-method-in-scala/34391003#34391003) <!-- --> ## T#5 Funktionen, Lambda, Funktionale Interfaces ### Funktionen * Ein **Funktionstyp (Function Type)** repräsentiert die Signatur (Eingabe- und Rückgabedatentypen) einer Funktion * Genauso wie Variablen und Konstanten einen Datentyp haben wenn ihnen Datenwerte zugewiesen werden, benötigen sie einen Funktionstyp wenn ihnen eine Funktion zugewiesen wird * Eingabe- und Rückgabedatentypen werden durch einen Pfeil => voneinander getrennt * Funktionstypen sind das Gegenstück von Functional Interfaces als Typ eines Lambda-Ausdrucks in Java 8 * Ein **Funktionswert oder Funktionsliteral** einer Variablen oder Konstanten ist eine Referenz auf die zugehörige Funktion * Eingabedatentypen werden geklammert (1), es sei denn, es existiert nur ein Parameter (2) * Ein Funktionstyp ohne Eingabeparameter wird durch leere Klammern gekennzeichnet (3) ```scala= var name : (type1, type2) => rückgabetyp = (param1:type1, param2:type2) => {} ``` <img src="https://i.imgur.com/jYt1FM3.png" width="370" height="320"> ### Typableitung bei Funktionstypen * Vollständige Typableitung funktioniert für Funktionstypen in der Regel nicht (1) * Ausnahme: die Zuweisung geschieht bereits über eine Variable oder Konstante, die bereits einen Funktionstyp hat - in diesem Fall funktioniert die Typableitung (3) <img src="https://i.imgur.com/WC61tnK.png" width="370" height="200"> ### Reine und nicht-reine Funktionen **Reine Funktionen** * Reine Funktionen (Proper Functions, 1) haben einen oder mehrere Eingabeparameter * Führen Berechnungen ausschließlich basierend auf Eingabeparametern aus * Liefern einen Wert zurück * Liefern immer den gleichen Wert für die gleiche Eingabe * Nutzen oder beeinflussen keinerlei Daten außerhalb der Funktion **Nicht-reine Funktionen** * Nicht-reine Funktionen (Non-proper Functions, 2) greifen auf nicht-lokale Variablen zu, die in übergeordneten Scopes oder Scopes anderer Objekte definiert werden Können beim Aufruf mit gleichen Werten unterschiedliche Ergebnisse liefern * Nutzen oder beeinflussen Daten außerhalb der Funktion (Seiteneffekte, Side Effects) * Beispiele: Dateizugriffe, Datenbankoperationen, Graphical User Interfaces, Netzwerkübertragungen <img src="https://i.imgur.com/FHlE2IF.png" width="370" height="250"> ### Deklarative Funktionen vs. anonyme Funktionen * Funktionswerte können auf zwei verschiedene Arten angelegt werden * Als deklarierte Funktion, d.h. zuerst die Deklaration, gefolgt von den Variablennamen und der Implementierung der Funktion * Als anonyme Funktion, d.h. als Funktion ohne Deklaration und Angabe des Funktionstyps * Typ des Ergebnisse bei der anonymen Funktion wird, wenn möglich, durch Typableitung bestimmt * Gegenüber der Aufteilung in einen Funktionstyp mit nachfolgendem Funktionsliteral bei einer deklarierten Funktion ist die Angabe einer anonymen Funktion kürzer, da sie diese beiden Teile miteinander vereint ```scala= var name : (type1, type2) => rückgabetyp = (param1:type1, param2:type2) => {} // Deklarative Funktion var name = (param1:type1, param2:typ2) => {} // Anonyme Funktion ``` <img src="https://i.imgur.com/Yp2iuss.png" width="370" height="290"> <img src="https://i.imgur.com/Jj1Kzku.png" width="370" height="200"> <br><br> ### Implementierung des Runnable Interfaces ```java public interface Runnable // Functional interface { public void run(); } ``` ```java public class Thread { public Thread() {...} public Thread(Runnable r) {...} ... } ``` ```java public class HelloWorld implements Runnable{ @Override public void run() { System.out.println("Hello World!"); } } public class Prog implements Runnable{ @Override public void run() { System.out.println("Prog2!"); } public static void main(String[] args) { Runnable run = new Prog(); new Thread(run).start(); new Thread(new HelloWorld()).start(); } } ``` ### Anonyme Klassen * Anonyme Klassen sind ein Spezialfall lokaler Klassen - sie haben keinen Namen und werden stets im Argument einer new-Anweisung definiert ```java public static void main(String[] args) { Runnable run = new Runnable() { @Override public void run() { System.out.println("Hello World!"); } }; new Thread(run).start(); new Thread(new Runnable() { @Override public void run() { System.out.println("Hello World!"); } }).start(); Thread prog = new Thread(){ @Override public void run() { System.out.println("Prog2!"); } }; prog.start(); } ``` ### Lambda-Ausdrücke * Ein Lambda-Ausdruck repräsentiert eine anonyme Funktion und besteht aus einer Parameterliste, dem Operator -> und einem Rumpf * Lambda-Ausdrücke wurden mit Java 8 eingeführt und machen aus Java eine objektorientierte Programmiersprache mit funktionalen Elementen ![](https://i.imgur.com/vp4v0rw.png) **Beispiele:** ```java= (List<String> list) -> list.isEmpty() // Boole'scher Ausdruck () -> new Apple(10) // Kreierung eines Objekts (Apple a) -> {System.out.printl(a.getWeight();} // Nutzen eines Objekts ..... ``` **Implementierung des Runnable Interfaces** ```java= Runnable run = (() -> System.out.println("Hello World!")); Thread t1 = new Thread(run); t1.start(); new Thread(() -> System.out.println("Hello World!")).start(); ``` ### Vorgefertigte functional Interfaces in Java 8 ![](https://i.imgur.com/TNqDWsB.jpg) ```java= public interface Predicate<T> { boolean test(T t); } Predicate<Integer> pr = (Integer x) -> x == 1; System.out.println(pr.test(1)); // true boolean test(Integer x){ return x== 1; } Consumer<Integer> cs = (x) -> System.out.println(x); cs.accept(423); // 423 Function<Integer, Integer> fu = (Integer x) -> x*10; System.out.println(fu.apply(10)); // 100 UnaryOperator<Integer> ua = (Integer x) -> x*10; System.out.println(ua.apply(10)); Supplier<Thread> tr = () -> new Thread(); tr.get().start(); BinaryOperator<Integer> bi = (x,y) -> x+y; System.out.println(bi.apply(3,4)); ``` **Was sind denn "Primitive Specializations"?** * Generische Parameter können nur an **Referenztypen** gebunden werden * **Boxing** ist ein Mechanismus in Java, um primitive Typen in Referenztypen (z.B. int zu Integer) zu verwandeln, Unboxing bezeichnet den umgekehrten Vorgang * **Autoboxing** führt je nach Bedarf Boxing oder Unboxing automatisch durch * Problem: "Boxed Values" benötigen mehr Speicherplatz und verursachen mehr Speicherzugriffe als Werte primitiver Typen * Primitive Specializations sind funktionale Interfaces, die nur auf primitiven Datentypen basieren und Autoboxing nicht zulassen * Empfehlung: bevorzuge Primitive Specializations wenn möglich ## T#6 Streams, Scala Klassen ### Streams <img src="https://i.imgur.com/BssiPpI.png" width="420" height="240"> <br><br> **Bestandteile von Stream-Pipelines** * Datenquelle (z.B. eine Collection) als Anfang der Pipeline * Intermediate Operations als Zwischenbearbeitungsschritte * Terminal Operation als Abschluss der Pipeline zur Berechnung eines Ergebnisses **Intermediate Operations** * Intermediate Operations verwenden einen Stream als Eingabe und Produzieren einen Stream als Ausgabe als Ergebnis der Operation * Intermediate Operations sind lazy (faul, träge), d.h. sie werden erst dann ausgeführt, wenn die Terminal Operation der Pipeline aufgerufen wird * **Bsp.:** map(), flatMap(), filter(), unsorted(), sorted(), distinct(), limit(), peek(), etc. **Terminal Operations** * Terminal Operations produzieren das Ergebnis einer Pipeline, welches jeden Datentyp (List, Integer, void) mit Ausnahme von Stream haben kann * Jede Pipeline muss mit einer Terminal Operation abgeschlossen werden * **Bsp.:** sum(), min(), max(), count(), average(), collect(), reduce(), etc. ### Klassen * Eine Klasse ist eine Schablone für die Erzeugung von Instanzen * Werden mit dem Schlüsselwort class angelegt * Enthalten Felder und Methoden * Instanzen einer Klasse werden mit Konstruktoren und dem Schlüsselwort new angelegt ```scala= class User(val name: String) { def greet: String=s"Grüße von $name" override def toString=s"User($name)" } scala> val kevin=new User("Kevin") scala> kevin.greet // res50: String = "Grüße von Kevin" ``` ### Klassenparameter * Klassenparameter sind Eingabevariablen, die bei Instanziierung der Klasse übergeben werden, und die automatisch zu Feldern der Klasse werden * Klassenparameter, die ohne val oder var deklariert werden, sind private val (!!!) und von außerhalb der Klasse nicht zugreifbar und nach der Instanziierung auch nicht mehr veränderbar (weil val) * Ergänzung von Klassenparametern mit dem Präfix val oder var macht sie zu Feldern, die public sind ```scala= class User(name: String) { val status: String="Studierender" def greet: String=s"Grüße von $name" override def toString=s"User($name)" } scala> val u=new User("Bernd") scala> u.name // Main.scala:25: value name is not a member of cmd67.INSTANCE.$ref$cmd66.User ``` ```scala= class User2(val name: String) { val status: String="Studierender" def greet: String=s"Grüße von $name" override def toString=s"User($name)" } scala> val u2=new User2("Bernd") scala> u2.name // Bernd ``` ### Primäre Konstruktoren - Jeder Klasse hat einen primären Konstruktor (Primary Constructor), der implizit durch die Klassenparameter und den Rumpf der Klasse gegeben ist - Keine explizite Definition (vgl. Java) - Anweisungen, Ausdrücke und Felder außerhalb von Methoden im Rumpf der Klasse gehören zum primären Konstruktor - Klassenparameter werden durch den primären Konstruktor zu Instanzvariablen des Objektes und mit den übergebenden Werten initialisiert ```scala= class Person(val name: String, val age: Int) { println("Eine neue Person wurde angelegt") } ``` ### Hilfskonstruktoren - Eine Klasse hat optional Hilfskonstruktoren (Auxiliary Constructors) - Hilfskonstruktoren werden mit this benannt - Beginnen mit einem Aufruf eines zuvor definierten Hilfskonstruktors oder des primären Konstruktors - Hilfskonstruktoren einer Klasse müssen unterschiedliche Signaturen haben - Definieren keinen Rückgabetyp (nicht mal Unit) ```scala= class Person { private var name="" private var age=0 def this(name: String)={ this() this.name=name } def this(name: String, age: Int)={ this(name) this.age=age } } scala> val p1=new Person scala> val p2=new Person("Thomas") scala> val p3=new Person("Claudia", 26) ``` ### Getter und Setter-Methoden - Scala generiert automatisch Getter- und Setter-Methoden für alle Felder, die nicht mit private gekennzeichnet sind - Konstanten (val) haben lediglich Getter-, aber keiner Setter-Methoden - Beispiel: age - Getter-Methode: age - Setter-Methode: age_= - Lesezugriff auf age wird intern durch den Aufruf einer Methode age abgebildet - Schreibzugriff auf age wird intern durch den Aufruf einer Methode age_= abgebildet - Anstelle der automatisch generierten Getter- und Setter-Methoden können in Scala eigene Getter- und Setter-Methoden definiert werden - Beachte: der Name der Setter-Methode ist "age&lowbar;=" (&lowbar; repräsentiert das Leerzeichen, welches beim Aufruf aber optional ist) - Getter- und Setter-Methoden repräsentieren ein nach außen sichtbares Feld, welches intern durch eine mit private gekennzeichnete Hilfsvariable _age realisiert wird Mehr zum Nachlesen * [Naming conventions](https://docs.scala-lang.org/style/naming-conventions.html#accessorsmutators) * [Noch zu setters/getters](https://www.oreilly.com/library/view/scala-cookbook/9781449340292/ch04s08.html) ```scala= class Person { private var _age=0 def age=_age def age_=(newAge: Int)={ if (newAge>_age) _age=newAge } } scala> val kai=new Person scala> kai.age_=(21) scala> kai.age scala> kai.age = 26 ``` ### Singletons * Klassen haben in Scala keine statischen Methoden und Felder * Singletons (Einzelstücke) sind Klassen von denen es maximal eine Instanz gibt, und ersetzen somit das Konzept statischer Methoden und Felder von Java * Erzeugung mit dem Schlüsselwort object ```scala= class User(val name: String) { val status: String="Studierender" def greet: String=s"Grüße von $name" override def toString=s"User($name)" } object Kevin{ def greet: String="Grüße von Kevin" override def toString="Kevin" def main(args: Array[String]): Unit={ val kevin=new User("Kevin") println(kevin.greet) println(Kevin.greet) } } ``` ### Companion-Objekte - Ein Companion-Objekt ergänzt eine Klasse mit demselben Namen innerhalb derselben Quelldatei um statische Methoden und Felder - Klasse und zugehöriges Companion-Objekt können gegenseitig auf privat deklarierte Methoden und Felder zugreifen - Hinweis: Methoden und Felder des Companion-Objektes müssen in der zugehörigen Klasse mit dem Objektnamen als Präfix aufgerufen werden - Hinweis: In REPL müssen Klasse und Companion-Objekt im Paste-Modus eingegeben werden, andernfalls behandelt REPL ihre Definition als wären sie in getrennten Quelldateien ```scala= class Account{ val id=Account.newUniqueNumber() private var balance=0.0 def deposit(amount: Double) {balance+=amount} } object Account{ private var lastNumber=0 private def newUniqueNumber()={lastNumber+=1; lastNumber} } scala> val mueller=new Account() // id = 1 scala> val schmitz=new Account() // id = 2 ``` ### Factory-Methoden - Eine Factory-Methode ermöglicht die Instanziierung eines Objektes einer Klasse und stellt eine Alternative zur Objekterzeugung mittels eines Konstruktors dar - Scala verwendet per Konvention apply als Factory-Methode innerhalb eines Companion-Objekts zur Erzeugung einer Instanz der zugehörigen Klasse - Methode apply instanziiert Objekte der Klasse durch Aufruf des Konstruktors der Klasse - Factory-Methode wird durch Klassenamen und Parameter aufgerufen, d.h. ohne apply und ohne das Schlüsselwort new ```scala= class Account private(val id: Int, initialBalance: Double) { private var balance=initialBalance def deposit(amount: Double)={balance+=amount} } object Account{ private var lastNumber=0 private def newUniqueNumber()={lastNumber+=1; lastNumber} def apply(initialBalance: Double)=new Account(newUniqueNumber(), initialBalance) } scala> val mueller=Account(1250) ``` ## Tutorium #8 ### Funktionen höherer Ordnung in Scala * Eine Funktion höherer Ordnung (Higher-order Function) ist eine Funktion, welche Funktionstypen als Eingabe- und/oder Rückgabeparameter hat * Eine Funktion höherer Ordnung muss den Funktionstyp der zu übergebenden Funktion definieren **Funktion als Eingabeparameter:** ```scala= val safeStringOp:(String,String=>String)=>String=(s,f)=>if (s!=null) f(s) else s //Definition einer Funktion höherer Ordnung val reverser:(String)=>String=s=>s.reverse val lowercase:(String)=>String=s=>s.toLowerCase safeStringOp("Ready",reverser) // res4: String = "ydaeR" safeStringOp("AOXOMOXOA", lowercase) //res6: String = "aoxomoxoa" ``` **Funktion als Rückgabeparameter:** ```scala= val dollarTo:String=>(Double=>Double)=currency=>{ if (currency=="GBP") dollar=>dollar*0.76 // (dollar:Double)=>dollar*0.76 else if (currency=="EUR") dollar=>dollar*0.83 else dollar=>dollar } val calculateTicketPrice:(Double=>Double)=>Double=currencyConversion=>{ currencyConversion(199.99) } calculateTicketPrice(dollarTo("EUR")) ``` **Wiederholung anonyme Funktion:** ```scala= (param1:type1, param2:typ2) => {} var name = (param1:type1, param2:typ2) => {} ``` ### Funktionen vs. Methoden ```scala= // Deklarative Funktion: var name : (type1, type2) => rückgabetyp = (param1:type1, param2:type2) => {} // Anonyme Funktion var name = (param1:type1, param2:typ2) => {} // Methode def name (param1:type1, param2:type2) : rückgabetype = {} ``` **Funktion** - Eine Funktion ist ein Wert eines Funktionstyps - Zur Laufzeit wird eine Funktion durch ein Objekt einer bestimmten Klasse repräsentiert - Eine val- oder var-Referenz auf eine Funktion zeigt auf das Objekt, welches die Funktion repräsentiert - Das Objekt, welches eine Funktion realisiert, hat eine Methode apply, welche die Parameter der Funktion als Methodenparameter und die Logik der Funktion in ihrem Rumpf umsetzt - Die apply-Methode kann mittel apply aufgerufen werden oder beim Aufruf weggelassen werden, wodurch sich die übliche Funktionsschreibweise ergibt - Weil Funktionen Objekte sind, erben sie die üblichen Methoden von AnyRef <img src="https://i.imgur.com/ffOnKuh.png" width="320" height="110"> <br> <br> ```scala= val isEven:Int=>Boolean=x=>x%2==0 scala> isEven(10) //true scala> isEven.apply(10) //true scala> isEven // res23: Int => Boolean = ammonite.$sess.cmd20$Helper$$Lambda$2322/0x0000000800b41840@7e6152e0 scala> isEven.toString() // res24: String = "ammonite.$sess.cmd20$Helper$$Lambda$2322/0x0000000800b41840@7e6152e0" ``` **Methode** - Eine Methode ist Mitglied eines Objekts und kann nicht außerhalb des Objekts eigenständig existieren - Methoden können nicht an Funktionen höherer Ordnung übergeben oder val-/var-Referenzen zugewiesen werden - Beachte: die REPL-Ausführungsumgebung wird durch ein Objekt realisiert, weshalb hier auf der Eingabe Methoden mittels def direkt definiert werden können - Eine Methode hat keinen Funktionstyp, jedoch eine Signatur, zum Beispiel (Int)Boolean <img src="https://i.imgur.com/824oOPk.png" width="210" height="140"> <br> <br> ```scala= object FilterExample { def isPositive(x:Int):Boolean=x>0 } scala> FilterExample.isPositive(-5) // false scala> FilterExample.isPositive.apply(-5) // Compilation Failed scala> FilterExample.isPositive //Compilation Failed scala> FilterExample.isPositive.toString() // Compilation Failed ``` **Konvertierung von Methoden in Funktion** - Methoden können in Funktionen umgewandelt werden, aber nicht umgekehrt - Umwandlung erfolgt entweder automatisch durch Übergabe einer Methodenreferenz an einen Parameter eines kompatiblen Funktionstyps beim Aufruf einer Funktion höherer Ordnung - Der Signatur der Methode muss hierbei dem Funktionstyp des aufnehmenden Parameters entsprechen - Oder Umwandlung erfolgt explizit, in dem die Methodenreferenz an eine val- oder var-Referenz übergeben wird - Der Funktionstyp der aufnehmenden val- oder var-Referenz wird aus der Signatur der Methode abgleitet, wenn bei der Zuweisung die Platzhalter-Syntax verwendet wird - Die **Eta-Expansion** konvertiert eine Methode in eine Funktion, in dem ein Objekt für die Funktion generiert wird und die apply-Methode des Objektes die ursprüngliche Methode aufruft ```scala= val filter:(Int=>Boolean,List[Int])=>List[Int]=(predicate,xs)=>for (x<-xs; if predicate(x)) yield x object FilterExample { def isPositive(x:Int):Boolean=x>0 } val posNums=filter(FilterExample.isPositive,List(-2,-1,0,1,2)) val isPositiveAsAFunction=FilterExample.isPositive //Compilation Failed //val isPositiveAsAFunction=FilterExample.isPositive() val isPositiveAsAFunction=FilterExample.isPositive _ //isPositiveAsAFunction: Int => Boolean = $Lambda$1068/0x000000080017d840@472719df filter(isPositiveAsAFunction,List(-2,-1,0,1,2)) ``` **Eta-Expansion** ```scala= object FilterExample { def isPositive(x:Int):Boolean=x>0 } val isPositiveAsAFunction=FilterExample.isPositive _ ``` ![](https://i.imgur.com/GJ73EBh.png) ### Java Methodenreferenzen * Wir verwenden Lambda-Ausdrücke um anonyme Funktionen zu erzeugen * Oft mach der Lambda-Ausdruck jedoch nichts anderes als eine bereits existierende Methode aufzurufen. **(Student s) -> s.getName()** * In solchen Fällen ist es häufig sinvoll die Referenz der existierende Methoden zu übergeben, woraus dann der entsprechende Lambda-Ausdruck erzeugt wird * Sie sind zusammengesetzt aus Zielreferenz, dem Trennzeichen “::” und dem Namen der Methode ohne klammern “()” * Es gibt insgesamt 4 verschiedene arten von Methodenreferenzen: auf statische Methoden, Instanzmethoden eines beliebigen Typs, Instanzmethoden eines existierenden Objekts und Konstruktoren. | | Methodenreferenz | Lambda-Ausdruck | | -------- | -------- | -------- | | Statische Methode | C::m | (a, b ..) -> C.m(a, b, ..) | | Instanzmethoden eines beliebigen Typs | C::m | (obj,a, b ..) -> obj.m(a, b, ..) | | Methode für ein Objekt obj | obj::m | (a, b ..) -> obj.m(a, b, ..) | | Konstruktor | C::new | (a, b ..) -> new C(a, b, ..) | 1) Statische Methoden: * Die Parameter der Methode werden zu Argumente im Lambda-Ausdruck * Im Lambda-Rumpf wird die Methode mit diesen Argumenten aufgerufen * aus **Math::abs** wird **x -> Math.abs(x)** ```scala= UnaryOperator<Double> absolutAsLambda = (Double value) -> Math.abs(value); System.out.println(absolutAsLambda.apply(-3.0)); UnaryOperator<Double> absolutAsRef = Math::abs; System.out.println(absolutAsRef.apply(-3.0)); ``` 2) Instanzmethoden eines beliebigen Typs: * Das aufgerufene Objekt muss bestimmt werden * Dem Lambda-Audruck wird neben den Parametern der Methode ein zusätzliches Argument für das Objekt eingeführt * Für dieses Objekt wird dann die Methode aufgerufen * aus **Object::toString** wird **(self) -> self.toString()** ```scala= Function<Thread, String> threadName = (threadObj) -> threadObj.getName(); System.out.println(threadName.apply(new Thread())); Function<Thread, String> threadNameRef= Thread::getName; System.out.println(threadNameRef.apply(new Thread())); ``` 3) Methode für ein konkretes Objekt: * Angabe des konkreten Objekts in einer Methodenreferenz * Wird verwendet wenn auf eine Methode referenziert wird, die Bestandteil eines externen, existierenden Objekts ist * aus **()->expensiveTransaction.getValue()** wird **expensiveTransaction::getValue** ```scala= Thread myThread = new Thread(); Supplier<String> tName = () -> myThread.getName(); System.out.println(tName.get()); Supplier<String> tNameRef = myThread::getName; System.out.println(tNameRef.get()); ``` 4) Konstruktoren: * aus **ArrayList<String>::new** wird **() -> new ArrayList<String>()** ```scala= Supplier<ArrayList<String>> listLambda = () -> new ArrayList<String>(); System.out.println(listLambda.get().size()); Supplier<ArrayList<String>> listRef = ArrayList<String>::new; System.out.println(listLambda.get().size()); ``` **Vorteile von Methodenreferenzen:** * kompakter als Lambda-Ausdrücke * einfacher zu lesen und verstehen ## Tutorium #9 ### Vorgefertigte functional Interfaces in Java 8 <img src="https://i.imgur.com/PYIDha5.png" width="470" height="390"> <br><br> | Functional Interface | abstract Method | Primitive Specializations | abstract Method | | -------------------- | --------------- | ------------------------- | --------------- | | Predicate<T>|test(T value)| IntPredicate, LongPredicate, DoublePredicate| test(int value), <br> test(long t),<br> test(double value), | | Consumer<T>| accept(T value)|IntConsumer, LongConsumer, DoubleConsumer | accept(int value), <br> accept(long value), <br> accept(double value) | | Function<T, R> | apply(T value) | IntFunction<R>, IntToDoubleFunction, ToIntFunction<T> , etc. | apply(int value), <br> applyAsDouble(int value), <br> applyAsInt(T value)| | Supplier<T>| get()| BooleanSupplier, IntSupplier, etc. | getAsBoolean(), getAsInt() | **Was sind denn "Primitive Specializations"?** * Generische Parameter können nur an **Referenztypen** gebunden werden * **Boxing** ist ein Mechanismus in Java, um primitive Typen in Referenztypen (z.B. int zu Integer) zu verwandeln, Unboxing bezeichnet den umgekehrten Vorgang * **Autoboxing** führt je nach Bedarf Boxing oder Unboxing automatisch durch * Problem: "Boxed Values" benötigen mehr Speicherplatz und verursachen mehr Speicherzugriffe als Werte primitiver Typen * Primitive Specializations sind funktionale Interfaces, die nur auf primitiven Datentypen basieren und Autoboxing nicht zulassen * Empfehlung: bevorzuge Primitive Specializations wenn möglich ```java= public interface IntPredicate { boolean test(int value); } IntPredicate pr = x -> x == 1; System.out.println(pr.test(1)); // true IntToDoubleFunction fn = x -> ((double) x )/2; System.out.println(fn.applyAsDouble(13)); ``` ### Klassen von Streams - Stream<int> bspw. nicht möglich, da Basisdatentypen nicht für Typparameter verwendet werden können - Für Basisdatentypen müssen daher geboxte Typen verwendet werden (Integer, Character, etc) - Arbeiten mit geboxten Typen ist nicht sehr effizient - Streamklassen für die Basisdatentypen int => **IntStream**, double => **DoubleStream** und long => **LongStream** - Methoden um den generischen Stream auf die Streams für die Basistypen abbilden: **mapToInt, mapToLong,** und **mapToDouble** - Methode um auf einen generischen Stream abzubilden: **mapToObj** ```java= double average = IntStream.range(1, 100) //IntStream .filter(x -> x%3 == 0) //IntStream .mapToObj(x -> x) //Stream<Integer> //.boxed() --> Hier besser anstelle von mapToObj .mapToInt(x -> x) //IntStream .average().getAsDouble(); ``` ### Kompatible Stream-Funktionen | Funktion | Funktionale Interfaces | | --------- | ---------------------- | | .filter() | Predicate | | .map() | Function, UnaryOperator| | .reduce() | BinaryOperator, BiFunction | | .collect()| Supplier, BiConsumer | | .foreach()| Consumer | https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html ## Tutorium #10 ### Vererbung - Eine Klasse (Subtyp) kann von **maximal einer anderen Klasse (Supertyp) mit dem Schlüsselwort extends** erben - **Konstruktor** der vererbenden Klasse muss angegeben werden und wird bei Instanziierung aufgerufen - Erbende Klasse kann Felder und Methoden der vererbenden Klasse mit dem **Schlüsselwort override** überschreiben - Verwendung von **override** ist bei Überschreiben von nicht-abstrakten Methoden und Feldern **verpflichtend**, nicht optional wie in Java - Felder und Methoden einer Klasse können mit this angesprochen werden, die der Superklasse mit dem Schlüsselwort super (nützlich wenn die erbende Klasse eine Methode überschreibt) ```scala= class Vehicle(val id: Int, val year: Int) { val color: String="red" override def toString=s"ID: $id Year: $year" } class Car(override val id: Int, override val year: Int, val fuelLevel: Int) extends Vehicle(id, year) { override val color: String="red" override def toString=s"${super.toString} Fuel Level: $fuelLevel" } scala> val car=new Car(1, 2015, 100) // car: Car = ID: 1 Year: 2015 Fuel Level: 100 ``` ### Abstrakte Klassen - Abstrakte Klassen dienen der **Erweiterung durch Subtypen**, können aber **nicht instanziiert** werden - Abstrakte Felder und abstrakte Methoden: Felder und Methoden in abstrakten Klassen die lediglich **deklariert, jedoch nicht mit einem Wert initialisiert** bzw. durch einen Methodenrumpf definiert werden - Abstrakte Felder und Methoden einer abstrakten Klasse sind nicht mit dem Schlüsselwort abstract gekennzeichnet - Abstrakte Felder und Methoden **müssen in der erbenden Klasse initialisiert bzw. definiert** werden - Verfügt eine Klasse über abstrakte Felder und Methoden, so muss diese Klasse abstrakt und mit dem Schlüsselwort abstract gekennzeichnet sein - **Abstrakte Felder und Methoden** der abstrakten Klasse werden in der erbenden Klasse **ohne das Schlüsselwort override** initialisiert bzw. definiert ```scala= abstract class Car { val year: Int val automatic: Boolean=true def color: String } scala> new Car() // Main.scala:25: class Car is abstract; cannot be instantiated new Car() class RedMini(val year: Int) extends Car { def color="Red" } scala> val m: Car=new RedMini(2005) // m: Car = cmd12$$user$RedMini@2a335ec ``` ### Vererbung mit Traits - Problem von Java und Scala: **Mehrfachvererbung wird nicht unterstützt** - **Traits (Mix-Ins)** sind klassenähnliche Verbünde von **abstrakten und nicht-abstrakten Feldern und Methoden**, die anderen Klassen "beigemischt" werden können - Traits haben abstrakte und nicht-abstrakte Methoden und Variablen - Traits werden zum Vererben mit extends an die erbende Klasse angehängt - mehrere Traits werden mit dem Schlüsselwort **with** voneinander getrennt - Wird von einer Klasse **und** von Traits geerbt, muss die **Klasse zuerst benannt** werden - Erbende Klasse muss alle abstrakten Methoden des Traits implementieren und alle abstrakten Variablen instanziieren - Nicht-abstrakte Methoden und Variablen des Traits können in der erbenden Klasse mit override überschrieben werden ```scala= trait Friend { val name: String def isFriend()=println(s"Your friend is $name") } trait Colleague { val name: String def isColleague()=println(s"Your colleague is $name") } class Human(val name: String) extends Colleague with Friend class Man(override val name: String) extends Human(name) class Woman(override val name: String) extends Human(name) scala> val john=new Man("John") scala> john.isFriend // Your friend is John ``` - Beimischung von Traits kann alternativ auch **pro Objekt erfolgen** - Vererbende(s) Trait(s) wird bei der Instanziierung des Objekts mit with angegeben - Methoden und Variablen können nach Angabe des Traits in einem folgenden Block überschrieben werden ```scala= class Cat(val name: String) extends Animal scala> val mizi=new Cat("Mizi") with Friend scala> mizi.isFriend // Your friend is Mizi scala> val mauzi=new Cat("Mauzi") with Friend { override def isFriend()=println(s"Your pet is $name") } ``` ## Tutorium #11 ### Generische Typen - Ein Typ-Parameter ist ein Platzhalter für einen Datentyp, der erst zur Laufzeit festgelegt wird - Typ-Parameter werden durch eckige Klammern repräsentiert und stehen immer vor runden oder geschweiften Klammern - Konvention: Typ-Parameter werden meistens durch Großbuchstaben repräsentiert, z.B. T (für Typ), E (für Element), K (für Key), V (für Value) ```scala= class Box[F](aFruit: F) { def fruit: F=aFruit } var appleBox=new Box[Apple](new Apple) var orangeBox=new Box[Orange](new Orange) ``` ### Typ-Schranken - Wahl eines konkreten Typs eines Typ-Parameters kann durch obere und untere Typ-Grenzen eingeschränkt werden - T <: Upperbound: Der Typ-Parameter muss ein Subtyp von Upperbound sein - T >: Lowerbound: Der Typ-Parameter muss ein Supertyp von Lowerbound sein - Verletzten die eingesetzten Datentypen die Typ-Einschränkungen kommt es zu einem Type-Mismatch-Fehler ```scala= abstract class Thing{} abstract class Vehicle extends Thing{} class Car extends Vehicle{} class Motorcycle extends Vehicle{} class Jeep extends Car class BMW extends Car class Vegetable{} class Parking[A <: Vehicle] // Upperbound new Parking[Vegetable]() // FEHLER! new Parking[Car]() new Parking[BMW]() // Lowerbound class Parking[A >: Jeep] new Parking[BMW]() // FEHLER! new Parking[Thing]() // Upperbound and Lowerbound class Parking[A >: Jeep <: Vehicle] new Parking[BMW]() // FEHLER! new Parking[Car]() ``` ### Typ-Varianzen https://blog.codecentric.de/2015/03/das-scala-typsystem-parametrisierte-typen-und-varianzen-teil-2/ ### Streams - Ein Stream ist eine Sequenz von Elementen, die von einer Quelle erzeugt und auf denen Operationen zur Datenverarbeitung angewendet werden <img src="https://i.imgur.com/BssiPpI.png" width="420" height="240"> <br><br> **Bestandteile von Stream-Pipelines** * Datenquelle (z.B. eine Collection) als Anfang der Pipeline * Intermediate Operations als Zwischenbearbeitungsschritte * Terminal Operation als Abschluss der Pipeline zur Berechnung eines Ergebnisses **Intermediate Operations** * Intermediate Operations verwenden einen Stream als Eingabe und Produzieren einen Stream als Ausgabe als Ergebnis der Operation * **Bsp.:** map(), flatMap(), filter(), unsorted(), sorted(), distinct(), limit(), peek(), etc. **Terminal Operations** * Terminal Operations produzieren das Ergebnis einer Pipeline, welches jeden Datentyp (List, Integer, void) mit Ausnahme von Stream haben kann * Jede Pipeline muss mit einer Terminal Operation abgeschlossen werden * **Bsp.:** sum(), min(), max(), count(), average(), collect(), reduce(), etc. **Zustandslose (Stateless) Operationen** * Operation basiert ausschließlich auf ihrer Funktionalität und dem jeweiligen Eingabeelement * Keine Speicherung von Zwischenergebnissen notwendig * **Bsp.:** filter(), map(), etc. **Zustandsbehaftete (Stateful) Operationen** * Ergebnis der Operation basiert auf ihrer Funktionalität, dem Eingabeelement und Kontextinformationen und/oder anderen Elementen * Kontextinformationen: Zähler oder Elemente die bereits bearbeitet wurden * **Bsp.:** sorted(), distinct(), etc. **Short-Circuiting (Kurzschluss-Operationen)** * Normale Stream-Operationen greifen auf alle Elemente des Input-Streams zu * Laziness ermöglich Short-Circuiting * Short-Circuiting-Operationen beenden zumeist die Bearbeitung einer Pipeline bevor sie alle Elemente gesehen hat <img src="https://i.imgur.com/ZjMv8E1.png" width="390" height="280"> <br><br> **Boxing und Unboxing** + Boxing ist ein Mechanismus in Java, um primitive Typen in Referenztypen (z.B. int zu Integer) zu verwandeln, + Unboxing bezeichnet den umgekehrten Vorgang ## Tutorium #12 ### Scala Collections | mutable Collection | immutable Collection | | ------------------ | -------------------- | | - veränderbar | - nicht veränderbar | | - Bsp.: MutableList kann vergrößert, verkleinert oder Elemente können überschrieben werden | - Bei einer "Veränderung" wird eine neue Collection erstellt und der Inhalt kopiert| <img src="https://i.imgur.com/4et6Mia.png" width="390" height="380"> <br><br> <img src="https://i.imgur.com/08sNgEq.png" width="590" height="450"> <br><br> <img src="https://i.imgur.com/2gvIxHU.png" width="650" height="550"> <br> ### Fold und Reduce * fold und reduce (und ihre Varianten foldLeft/Right und reduceLeft/Right) sind **Funktionen höherer Ordnung**, die rekursiv die Elemente einer Sequenz durchlaufen und in jedem Schritt gemäß der übergebenen Funktion ein Ergebnis in einer Variable speichern * Übergebene Funktion hat zwei Eingabeparameter: das **Ergebnis der letzten Operation** und das **gegenwärtige Element** * **fold** erlaubt die Angabe eines **Initialwertes** (Seed Value), **reduce nicht** * foldLeft und reduceLeft durchlaufen die Sequenz von links nach rechts, foldRight und reduceRight von rechts nach links ### FoldLeft (ReduceLeft) und FoldRight (ReduceRight) * foldLeft (reduceLeft) und foldRight (reduceRight) führen bei nicht-assoziativen Funktionen zu unterschiedlichen Ergebnissen <img src="https://i.imgur.com/UNykPXx.jpg" width="850" height="300"> <br> https://www.baeldung.com/scala/folding-lists ### Scala Streams * Im Unterschied zu Java Kollektionen haben Scala Kollektionen **Methoden zum Bearbeiten aller Elemente** (vergleichbar zu den Methoden, die Java Streams unterstützen) * Scala Kollektionen verfügen über **Transformer-Methoden**, die **eine Kollektion in eine andere überführen** * Vergleichbar zu **Intermediate-Operationen** für Java Streams * Trotzdem gibt eine Kollektion Stream in Scala. Warum? * **Transformer-Methoden** aller Kollektionen in Scala sind **strickt (eager, eifrig)**, d.h. eine neue Kollektion wird immer **vollständig aus der Eingabekollektion erzeugt** * Ausnahme: **Stream implementiert alle Transformer Methoden lazy (faul)** * **Achtung: Terminal-Operationen** wie sum, max oder size **erzwingen die Berechnung des gesamten Streams** und führen bei endlosen Streams zu einem Out-of-Memory-Fehler * Mit **force** wird die **Berechnung des gesamten Streams** erzwungen (nicht auf endlosen Streams aufrufen) ## Tutorium #13 ### Akka #### Aktoren * Ein Aktor ist in einem aktor-basierten System die **kleinste Einheit** (vergleichbar zu einem Objekt in einem objektbasierten System) * Ein Aktor kapselt Zustand und Verhalten (vergleichbar zu einem Objekt) * Ein Aktor **kommuniziert** mit der Außenwelt **über Nachrichten** - er empfängt Nachrichten und sendet Nachrichten * Nachrichten an einen Aktor werden in seiner **Inbox gespeichert**, d.h. der Nachrichtenaustausch ist asynchron * **Nachrichten** sind **unveränderliche (immutable)** Daten * Der Zweck eines Aktors besteht darin, die in seiner Inbox befindlichen Nachrichten zu verarbeiten * Außer dem Senden und Empfang von Nachrichten gibt es keine Möglichkeit, mit einem Aktor in Kontakt zu treten - es ist nicht möglich, auf seine Felder und Methoden zuzugreifen (im Gegensatz zu Objekten in einem objektorientierten System) <img src="https://i.imgur.com/Oacb6Gg.png" width="450" height="340"> <br><br> <img src="https://i.imgur.com/F3EIUmp.png" width="450" height="340"> <br><br> ```scala= package PingPong import akka.actor.{ Actor, ActorRef, ActorSystem, PoisonPill, Props } import language.postfixOps case object PingMessage case object Pongmessage case class StartMessage(r: ActorRef) case object StopMessage class Pinger extends Actor { var count = 0 def incrementAndPrint {count += 1; println("ping: " + count)} def receive = { case StartMessage(r) => incrementAndPrint r ! PingMessage case Pongmessage => incrementAndPrint if (count > 99) { sender ! StopMessage println("ping stopped") context.stop(self) } else { sender ! PingMessage } case _ => println("Ping got something unexpected!") } } class Ponger extends Actor { def receive = { case PingMessage => println(" pong") sender ! Pongmessage case StopMessage => println("pong stopped") context.stop(self) case _ => println("Pong got something unexpected") } } object Main extends App{ val system = ActorSystem("pingpong") val pinger = system.actorOf(Props[Pinger], "pinger") val ponger = system.actorOf(Props[Ponger], "ponger") pinger ! StartMessage(ponger) } ``` https://doc.akka.io/docs/akka/current/actors.html