Methoden-Referenzen
Seit Java8 können Referenzen auf Methoden statt anonymer Klassen eingesetzt werden (funktionales Interface nötig).
Dabei gibt es drei mögliche Formen:
- Form 1: Referenz auf eine statische Methode:
ClassName::staticMethodName
(wird verwendet wie(args) -> ClassName.staticMethodName(args)
) - Form 2: Referenz auf eine Instanz-Methode eines Objekts:
objectref::instanceMethodName
(wird verwendet wie(args) -> objectref.instanceMethodName(args)
) - Form 3: Referenz auf eine Instanz-Methode eines Typs:
ClassName::instanceMethodName
(wird verwendet wie(o1, args) -> o1.instanceMethodName(args)
)
Im jeweiligen Kontext muss ein passendes funktionales Interface verwendet werden, d.h. ein Interface mit genau einer abstrakten Methode. Die Methoden-Referenz muss von der Syntax her dieser einen abstrakten Methode entsprechen (bei der dritten Form wird die Methode auf dem ersten Parameter aufgerufen).
- (K2) Funktionales Interfaces (Definition)
- (K3) Einsatz von Methoden-Referenzen
Beispiel: Sortierung einer Liste
List<Studi> sl = new ArrayList<Studi>();
// Anonyme innere Klasse
Collections.sort(sl, new Comparator<Studi>() {
@Override public int compare(Studi o1, Studi o2) {
return Studi.cmpCpsClass(o1, o2);
}
});
// Lambda-Ausdruck
Collections.sort(sl, (o1, o2) -> Studi.cmpCpsClass(o1, o2));
// Methoden-Referenz
Collections.sort(sl, Studi::cmpCpsClass);
Anmerkung
Für das obige Beispiel wird davon ausgegangen, dass in der Klasse Studi
eine
statische Methode cmpCpsClass()
existiert:
public static int cmpCpsClass(Studi s1, Studi s2) {
return s1.getCps() - s2.getCps();
}
Wenn man im Lambda-Ausdruck nur Methoden der eigenen Klasse aufruft, kann man das auch direkt per Methoden-Referenz abkürzen!
- Erinnerung:
Comparator<T>
ist ein funktionales Interface - Instanzen können wie üblich durch Ableiten bzw. anonyme Klassen erzeugt werden
- Alternativ kann seit Java8 auch ein passender Lambda-Ausdruck verwendet werden
- Ab Java8: Referenzen auf passende Methoden (Signatur!) können ein funktionales
Interface "implementieren"
- Die statische Methode
static int cmpCpsClass(Studi s1, Studi s2)
hat die selbe Signatur wieint compare(Studi s1, Studi s2)
ausComparator<Studi>
- Kann deshalb wie eine Instanz von
Comparator<Studi>
genutzt werden - Name der Methode spielt dabei keine Rolle
- Die statische Methode
Überblick: Arten von Methoden-Referenzen
-
Referenz auf eine statische Methode
- Form:
ClassName::staticMethodName
- Wirkung: Aufruf mit
(args) -> ClassName.staticMethodName(args)
- Form:
-
Referenz auf Instanz-Methode eines bestimmten Objekts
- Form:
objectref::instanceMethodName
- Wirkung: Aufruf mit
(args) -> objectref.instanceMethodName(args)
- Form:
-
Referenz auf Instanz-Methode eines bestimmten Typs
- Form:
ClassName::instanceMethodName
- Wirkung: Aufruf mit
(arg0, rest) -> arg0.instanceMethodName(rest)
(arg0
ist vom TypClassName
)
- Form:
Anmerkung: Analog zur Referenz auf eine statische Methode gibt es noch die
Form der Referenz auf einen Konstruktor: ClassName::new
. Für Referenzen auf
Konstruktoren mit mehr als 2 Parametern muss ein eigenes passendes funktionales
Interface mit entsprechend vielen Parametern definiert werden ...
Methoden-Referenz 1: Referenz auf statische Methode
public class Studi {
public static int cmpCpsClass(Studi s1, Studi s2) {
return s1.getCredits() - s2.getCredits();
}
public static void main(String... args) {
List<Studi> sl = new ArrayList<Studi>();
// Referenz auf statische Methode
Collections.sort(sl, Studi::cmpCpsClass);
// Entsprechender Lambda-Ausdruck
Collections.sort(sl, (o1, o2) -> Studi.cmpCpsClass(o1, o2));
}
}
Collections.sort()
erwartet in diesem Szenario als zweiten Parameter eine Instanz von
Comparator<Studi>
mit einer Methode int compare(Studi o1, Studi o2)
.
Die übergebene Referenz auf die statische Methode cmpCpsClass
der Klasse Studi
hat die selbe Signatur und wird deshalb von Collections.sort()
genauso genutzt wie
die eigentlich erwartete Methode Comparator<Studi>#compare(Studi o1, Studi o2)
, d.h.
statt compare(o1, o2)
wird nun für jeden Vergleich Studi.cmpCpsClass(o1, o2)
aufgerufen.
Methoden-Referenz 2: Referenz auf Instanz-Methode (Objekt)
public class Studi {
public int cmpCpsInstance(Studi s1, Studi s2) {
return s1.getCredits() - s2.getCredits();
}
public static void main(String... args) {
List<Studi> sl = new ArrayList<Studi>();
Studi holger = new Studi("Holger", 42);
// Referenz auf Instanz-Methode eines Objekts
Collections.sort(sl, holger::cmpCpsInstance);
// Entsprechender Lambda-Ausdruck
Collections.sort(sl, (o1, o2) -> holger.cmpCpsInstance(o1, o2));
}
}
Collections.sort()
erwartet in diesem Szenario als zweites Argument wieder eine Instanz
von Comparator<Studi>
mit einer Methode int compare(Studi o1, Studi o2)
.
Die übergebene Referenz auf die Instanz-Methode cmpCpsInstance
des Objekts holger
hat die selbe Signatur und wird entsprechend von Collections.sort()
genauso genutzt wie
die eigentlich erwartete Methode Comparator<Studi>#compare(Studi o1, Studi o2)
, d.h.
statt compare(o1, o2)
wird nun für jeden Vergleich holger.cmpCpsInstance(o1, o2)
aufgerufen.
Methoden-Referenz 3: Referenz auf Instanz-Methode (Typ)
public class Studi {
public int cmpCpsInstance(Studi studi) {
return this.getCredits() - studi.getCredits();
}
public static void main(String... args) {
List<Studi> sl = new ArrayList<Studi>();
// Referenz auf Instanz-Methode eines Typs
Collections.sort(sl, Studi::cmpCpsInstance);
// Entsprechender Lambda-Ausdruck
Collections.sort(sl, (o1, o2) -> o1.cmpCpsInstance(o2));
}
}
Collections.sort()
erwartet in diesem Szenario als zweites Argument wieder eine Instanz
von Comparator<Studi>
mit einer Methode int compare(Studi o1, Studi o2)
.
Die übergebene Referenz auf die Instanz-Methode cmpCpsInstance
des Typs Studi
hat
die Signatur int cmpCpsInstance(Studi studi)
und wird von Collections.sort()
so genutzt:
Statt compare(o1, o2)
wird nun für jeden Vergleich o1.cmpCpsInstance(o2)
aufgerufen.
Ausblick: Threads
Erinnerung an bzw. Vorgriff auf “Threads: Intro”:
public interface Runnable {
void run();
}
Damit lassen sich Threads auf verschiedene Arten erzeugen:
public class ThreadStarter {
public static void wuppie() { System.out.println("wuppie(): wuppie"); }
}
Thread t1 = new Thread(new Runnable() {
public void run() {
System.out.println("t1: wuppie");
}
});
Thread t2 = new Thread(() -> System.out.println("t2: wuppie"));
Thread t3 = new Thread(ThreadStarter::wuppie);
Ausblick: Datenstrukturen als Streams
Erinnerung an bzw. Vorgriff auf “Stream-API”:
class X {
public static boolean gtFour(int x) { return (x > 4) ? true : false; }
}
List<String> words = Arrays.asList("Java8", "Lambdas", "PM",
"Dungeon", "libGDX", "Hello", "World", "Wuppie");
List<Integer> wordLengths = words.stream()
.map(String::length)
.filter(X::gtFour)
.sorted()
.collect(toList());
- Collections können als Datenstrom betrachtet werden:
stream()
- Iteration über die Collection, analog zu externer Iteration mit
foreach
- Iteration über die Collection, analog zu externer Iteration mit
- Daten aus dem Strom filtern:
filter
, braucht Prädikat - Auf alle Daten eine Funktion anwenden:
map
- Daten im Strom sortieren:
sort
(auch mit Comparator) - Daten wieder einsammeln mit
collect
=> Typische Elemente funktionaler Programmierung
=> Verweis auf Wahlfach "Spezielle Methoden der Programmierung"
Wrap-Up
Seit Java8: Methoden-Referenzen statt anonymer Klassen (funktionales Interface nötig)
-
Drei mögliche Formen:
- Form 1: Referenz auf statische Methode:
ClassName::staticMethodName
(verwendet wie(args) -> ClassName.staticMethodName(args)
) - Form 2: Referenz auf Instanz-Methode eines Objekts:
objectref::instanceMethodName
(verwendet wie(args) -> objectref.instanceMethodName(args)
) - Form 3: Referenz auf Instanz-Methode eines Typs:
ClassName::instanceMethodName
(verwendet wie(o1, args) -> o1.instanceMethodName(args)
)
- Form 1: Referenz auf statische Methode:
-
Im jeweiligen Kontext muss ein passendes funktionales Interface verwendet werden (d.h. ein Interface mit genau einer abstrakten Methode)
In den Vorgaben
finden Sie die Klassen Student
und StudentSort
mit
vorgefertigten Methoden zu den Teilaufgaben sowie eine Testsuite
SortTest
mit einzelnen Testfälllen zu den Teilaufgaben, mit der Ihre
Implementierung aufgerufen und getestet wird.
Ziel dieser Aufgabe ist es, eine Liste von Studierenden mithilfe verschiedener syntaktischer Strukturen (Lambda-Ausdrücke, Methoden-Referenzen) zu sortieren. Dabei soll bei allen Teilaufgaben die Methode java.util.List#sort für das eigentliche Sortieren verwendet werden.
-
Erweitern Sie die Klasse
Student
um eine statische Methode, die zweiStudent
-Objekte anhand des Alters miteinander vergleicht. Die Methode soll die Signaturstatic int compareByAge(Student a, Student b)
besitzen und die folgenden Werte zurückliefern:- a > b -> -1
- a < b -> 1
- a == b -> 0
Verwenden Sie die neue statische Methode
compareByAge
zum Sortieren der Liste insort_2b()
. Nutzen Sie dabei eine Methodenreferenz. -
Erweitern Sie die Klasse
Student
um eine Instanz-Methode, die dasStudent
-Objekt mit einem anderen (als Parameter übergebenen)Student
-Objekt vergleicht. Die Methode soll die Signaturint compareByName(Student other)
besitzen und die folgenden Werte zurückliefern:- self > other -> -1
- self < other -> 1
- self == other -> 0
Verwenden Sie die neue Methode
compareByName
zum Sortieren der Liste insort_3b()
. Nutzen Sie dabei eine Methodenreferenz.
- [Urma2014] Java 8 in Action: Lambdas, Streams, and Functional-Style Programming
Urma, R.-G. und Fusco, M. und Mycroft, A., Manning Publications, 2014. ISBN 978-1-6172-9199-9.
Kapitel 3: Lambda Expressions, Kapitel 5: Working with streams