1. Einführung
Method reference (auf Deutsch – Methodenreferenz) ist eine spezielle Syntax in Java, die es erlaubt, eine bereits existierende Methode (oder einen Konstruktor) als Implementierung eines funktionalen Interfaces zu übergeben. Eine Methode kann dort „weitergereicht“ werden, wo ein Lambda-Ausdruck erwartet wird, sofern die Signaturen übereinstimmen.
Syntax:
Class::method
object::method
Class::new
Wenn eine Lambda so etwas wie eine kleine Funktion „on the fly“ ist, dann bedeutet eine Methodenreferenz einfach: „gib die bereits vorhandene Methode weiter“. Das ist wie statt des Abschreibens eines Rezepts den Link auf die Rezeptseite zu geben.
Anschauliches Beispiel
Stattdessen:
list.forEach(s -> System.out.println(s));
Kann man so schreiben:
list.forEach(System.out::println);
Sieht doch kompakt aus, oder?
2. Arten von Methodenreferenzen
Es gibt vier Hauptformen von Methodenreferenzen. Das sind sie – mehr braucht man nicht.
Verweis auf eine statische Methode
Syntax: Class::staticMethod
Beispiel:
Function<Integer, String> intToString = String::valueOf;
System.out.println(intToString.apply(123)); // "123"
Dasselbe mit einer Lambda:
Function<Integer, String> intToString = i -> String.valueOf(i);
Verweis auf eine Instanzmethode eines Objekts
Syntax: object::method
Beispiel:
PrintStream printer = System.out;
Consumer<String> consumer = printer::println;
consumer.accept("Hallo, Welt!");
Entspricht einer Lambda:
Consumer<String> consumer = s -> printer.println(s);
Verweis auf eine Instanzmethode einer Klasse
Syntax: Class::method
Hier wird der erste Parameter des funktionalen Interfaces zu dem Objekt, auf dem die Methode aufgerufen wird.
Beispiel:
Function<String, Integer> stringLength = String::length;
System.out.println(stringLength.apply("Java")); // 4
Hier wird String::length zu einer Funktion: (String s) -> s.length()
Verweis auf einen Konstruktor
Syntax: Class::new
Beispiel:
Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();
Entspricht einer Lambda:
Supplier<ArrayList<String>> listSupplier = () -> new ArrayList<>();
3. Wann sollte man Methodenreferenzen verwenden?
Methodenreferenzen sind praktisch, wenn eine Lambda lediglich eine bereits existierende Methode ohne zusätzliche Logik aufruft – so wird der Code kürzer und besser lesbar.
Beispiel: Liste sortieren
Stattdessen:
List<String> names = Arrays.asList("John", "Peter", "Anna");
names.sort((a, b) -> a.compareToIgnoreCase(b));
Kann man so schreiben:
names.sort(String::compareToIgnoreCase);
Beispiel: Kollektionen verarbeiten
Statt:
list.forEach(s -> System.out.println(s));
Noch kürzer:
list.forEach(System.out::println);
Beispiel: Elemente transformieren
Statt:
List<String> numbers = Arrays.asList("1", "2", "3");
List<Integer> ints = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
Möglich ist auch:
List<Integer> ints = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
Mehr über das Stream API und die Hilfsklasse Collectors erfahren Sie auf Level 30 :P
4. Vergleich: Methodenreferenz und Lambda-Ausdruck
Äquivalenz
Methodenreferenz und Lambda-Ausdruck sind oft austauschbar. Beide implementieren ein funktionales Interface, wenn die Signaturen übereinstimmen.
Beispiel:
Consumer<String> c1 = s -> System.out.println(s);
Consumer<String> c2 = System.out::println;
Wann ist eine Methodenreferenz sinnvoller?
- Wenn die Lambda lediglich eine existierende Methode ohne zusätzliche Logik aufruft.
- Zur Steigerung der Lesbarkeit, insbesondere in langen Aufrufketten.
- Wenn man explizit zeigen möchte: „hier wird einfach nur eine Methode aufgerufen“.
Wann ist eine Methodenreferenz ungeeignet?
- Wenn zusätzliche Logik nötig ist (Validierung, Bedingungen, Fehlerbehandlung).
- Wenn Parameter vor dem Methodenaufruf transformiert werden müssen.
Beispiel:
list.forEach(s -> {
if (s != null) System.out.println(s);
});
// Hier ist eine Methodenreferenz nicht geeignet, nur eine Lambda.
5. Praxis: Lambdas mit Methodenreferenzen umschreiben
Beispiel 1: Tiernamen ausgeben
List<String> animals = Arrays.asList("Katze", "Hund", "Papagei");
animals.forEach(animal -> System.out.println(animal));
Wird zu:
animals.forEach(System.out::println);
Beispiel 2: Strings in Zahlen umwandeln
List<String> numbers = Arrays.asList("10", "20", "30");
List<Integer> ints = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
Wird zu:
List<Integer> ints = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
Beispiel 3: Objekte nach Namen sortieren
List<Animal> animalList = ...;
animalList.sort((a, b) -> a.getName().compareTo(b.getName()));
Wird zu:
animalList.sort(Comparator.comparing(Animal::getName));
Hier ist Animal::getName ein Verweis auf eine Instanzmethode der Klasse.
Beispiel 4: Objekte über einen Konstruktor erstellen
Supplier<Dog> dogFactory = () -> new Dog();
Dog dog = dogFactory.get();
Wird zu:
Supplier<Dog> dogFactory = Dog::new;
Dog dog = dogFactory.get();
6. Wie funktioniert die Signaturzuordnung?
Eine Methodenreferenz kann nur verwendet werden, wenn die Methodensignatur mit der des abstrakten Methodenmembers des funktionalen Interfaces übereinstimmt.
@FunctionalInterface
interface IntToString {
String convert(int value);
}
public class Demo {
public static String intToHex(int value) {
return Integer.toHexString(value);
}
public static void main(String[] args) {
IntToString converter = Demo::intToHex;
System.out.println(converter.convert(255)); // ff
}
}
Hier passt Demo::intToHex, weil es ein int entgegennimmt und ein String zurückgibt.
7. Methodenreferenzen und Konstruktoren mit Parametern
Wenn ein Konstruktor Parameter entgegennimmt, kann man dennoch eine Methodenreferenz verwenden – bei übereinstimmenden Signaturen.
@FunctionalInterface
interface AnimalFactory {
Animal create(String name);
}
class Animal {
private String name;
public Animal(String name) { this.name = name; }
public String getName() { return name; }
}
AnimalFactory factory = Animal::new;
Animal cat = factory.create("Felix");
System.out.println(cat.getName()); // Felix
8. Typische Fehler bei der Verwendung von Methodenreferenzen
Fehler Nr. 1: Abweichende Signaturen.
Wenn die Methodensignatur nicht mit der des abstrakten Interface-Methods übereinstimmt, meldet der Compiler einen Fehler. Beispielsweise erwartet das Interface zwei Parameter, die Referenz zeigt jedoch auf eine Methode mit nur einem Parameter.
Fehler Nr. 2: Versuch, auf eine Instanzmethode der Klasse ohne Objekt zu verweisen.
Wenn Sie die Form Class::method verwenden, wird der erste Parameter des Interfaces zum aufrufenden Objekt. Wenn Anzahl oder Reihenfolge der Parameter verwechselt werden, kommt es zu einem Zuordnungsfehler.
Fehler Nr. 3: Einsatz einer Methodenreferenz, wo zusätzliche Logik nötig ist.
Wenn Bedingungen, Logging oder Ausnahmebehandlung erforderlich sind, verwenden Sie eine Lambda und keine Methodenreferenz.
Fehler Nr. 4: Verweise auf überladene Methoden.
Wenn eine Klasse mehrere Methoden mit demselben Namen hat, kann der Compiler unter Umständen nicht entscheiden, welche gewählt werden soll. Manchmal hilft die explizite Angabe des Typs des funktionalen Interfaces.
Fehler Nr. 5: Verwendung eines Verweises auf eine Instanzmethode ohne Objekt.
Zum Beispiel funktioniert String::toUpperCase in map korrekt, weil der erste Parameter das Objekt String selbst ist. Außerhalb eines passenden Kontexts, in dem eine statische Methode erwartet wird, führt dies jedoch zu einem Fehler.
GO TO FULL VERSION