Je größer Software-Systeme werden, desto wichtiger werden Aspekte wie Klarheit, Wiederverwendbarkeit und Dokumentation. Wir haben in für unseren String-Comparator eine Implementierung geschrieben, anfangs über eine innere Klasse, später über einen Lambda-Ausdruck, in jedem Fall haben wir Code geschrieben. Doch was wäre, wenn eine Utility-Klasse schon eine Implementierung hätte? Kann könnte der Lambda-Ausdruck natürlich an die vorhandene Implementierung delegieren.
class StringUtils { public static int compareTrimmed( String s1, String s2 ) { return s1.trim().compareTo( s2.trim() ); } } public class CompareIgnoreCase { public static void main( String[] args ) throws Exception { String[] words = { "A", "B", "a" }; Arrays.sort( words, (String s1, String s2) -> StringUtils.compareTrimmed(s1, s2) ); System.out.println( Arrays.toString( words ) ); } }
Auffällig bei dem Beispiel ist, dass die referenzierte Methode compareTrimmed(String,String) von den Parametertypen und vom Rückgabetyp genau auf die compare(…)-Methode eines Comparator passt. Für genau solche Fälle gibt es eine weitere syntaktische Verkürzung, dass Entwickler im Code kein Lambda-Ausdruck mehr schreiben müssen.
Definition: Methoden-Referenzen identifizieren Methoden ohne sie aufzurufen. Syntaktisch trennen zwei Doppelpunkte den Klassenamen bzw. die Referenz auf der linken Seite von einem Methodennamen auf der rechten.
Die Zeile
Arrays.sort( words, (String s1, String s2) -> StringUtils.compareTrimmed(s1, s2) );
lässt sich mit Methoden-Referenzen abkürzen zu:
Arrays.sort( words, StringUtils::compareTrimmed );
Die Sortiermethode erwartet vom Comparator eine Methode, die zwei Strings annimmt und eine Ganzzahl zurückgibt. Der Name der Klasse und der Name der Methode ist unerheblich, weshalb Methoden-Referenzen eingesetzt werden können.
Eine Methoden-Referenz ist wie ein Lambda-Ausdruck ein Exemplar einer funktionalen Schnittstelle, jedoch für eine existierende Methode einer bekannten Klasse. Wie üblich bestimmt der Kontext von welchem Typ genau der Ausdruck ist.
Beispiel: Gleicher Code für eine Methoden-Referenz kann zu komplett unterschiedlichen Typen führen – der Kontext macht den Unterschied:
Comparator<String> c = StringUtils::compareTrimmed;
BiFunction<String, String, Integer> c = StringUtils::compareTrimmed;
Im Beispiel war die Methode compareTrimmed(…) statisch, und links vom Doppeltpunkt steht der Name einer Klasse stehen. Doch kann links auch eine Referenz stehen, was dann eine Objektmethode referenziert.
Beispiel: Die statische Variable String.CASE_INSENSITIVE_ORDER enthält eine Referenz auf ein Comparator-Objekt:
Comparator<String> c = String.CASE_INSENSITIVE_ORDER;
Wir können auch mit Methoden-Referenzen arbeiten:
Comparator<String> c = String.CASE_INSENSITIVE_ORDER::compare;
Statt dass der Name einer Referenzvariablen gewählt wird, kann auch this das Objekt beschreiben.
Was soll das alles?
Für Einsteiger in die Sprache Java wird dieses Sprache-Feature wie der größte Zauber auf Erden vorkommen und auch Java-Profis bekommen hier zittrige Finger, entweder vor Angst oder Freunde… In der Vergangenheit musste in Java sehr viel explizit geschrieben werden, aber mit diesen neuen Methoden-Referenzen sieht und macht der Compiler vieles von selbst.
Nützlich wird diese Eigenschaft mit den funktionalen Bibliotheken aus Java 8, die ein eigenes Kapitel einnehmen. Nur kurz:
String[] words = { "3", "2", " 1", "" }; Arrays.stream( words ) .map( String::trim ) .filter( (s) -> s != null && ! s.isEmpty() ) .map( Integer::parseInt ) .sorted() .forEach( System.out::println ); // 1 2 3
Schreibfehler in:
„Im Beispiel war die Methode compareTrimmed(…) statisch, und links vom Doppeltpunkt steht der Name einer Klasse stehen.“
Gruß
Stephan
Ich hab auch noch einen Schreibfehler gefunden!
Du schreibst oben „Kann könnte der Lambda-Ausdruck“
Ich bin mir fast sicher, dass das nicht so sein sollte ^^