Warum stimmt eine Java-Methodenreferenz mit Rückgabetyp mit der Consumer-Schnittstelle überein?

9

Ich bin verwirrt durch den folgenden Code

%Vor%

Ich hätte erwartet, dass die Zuweisung von lambda3 fehlschlägt, da meine consume-Methode nicht mit der accept-Methode in der Verbraucherschnittstelle übereinstimmt - die Rückgabetypen sind unterschiedlich, String vs. void.

Außerdem dachte ich immer, dass es eine Eins-zu-eins-Beziehung zwischen Lambda-Ausdrücken und Methodenreferenzen gibt, aber dies ist eindeutig nicht der Fall, wie mein Beispiel zeigt.

Könnte mir jemand erklären, was hier passiert?

    
Ulrich Schmidt 18.05.2016, 19:15
quelle

2 Antworten

12

consume(String) Methode entspricht Consumer<String> interface, weil sie String verbraucht - die Tatsache, dass sie einen Wert zurückgibt, ist irrelevant, da sie in diesem Fall einfach ignoriert wird. (Weil die Schnittstelle Consumer überhaupt keinen Rückgabewert erwartet.)

Es muss eine Designauswahl und im Grunde ein Dienstprogramm gewesen sein: Stellen Sie sich vor, wie viele Methoden refactored oder dupliziert werden müssten, um den Anforderungen funktionaler Interfaces wie Consumer oder sogar dem sehr häufigen Runnable zu entsprechen. (Beachten Sie, dass Sie jede Methode übergeben können, die keine Parameter als Runnable an zB Executor verwendet.)

Auch Methoden wie java.util.List#add(Object) geben einen Wert zurück: boolean . Solche Methodenverweise nur deshalb nicht weitergeben zu können, weil sie etwas zurückgeben (was in vielen Fällen meistens irrelevant ist), wäre eher ärgerlich.

    
JustACluelessNewbie 18.05.2016, 19:31
quelle
9

Als Brian Goetz wies darauf hin In einem Kommentar war die Grundlage für die Design-Entscheidung die Anpassung einer Methode zu einer funktionalen Schnittstelle genauso wie Sie die Methode aufrufen können, dh Sie können jede Rückgabewertmethode aufrufen und den zurückgegebenen Wert ignorieren.

Wenn es um Lambda-Ausdrücke geht, werden die Dinge ein bisschen komplizierter. Es gibt zwei Formen von Lambda-Ausdrücken, (args) -> expression und (args) -> { statements* } .

Ob die zweite Form void -kompatibel ist, hängt von der Frage ab, ob alle Code-Pfade, die normal beendet werden, nicht versuchen werden, einen Wert zurückzugeben, z. () -> { return ""; } ist nicht void kompatibel, aber Ausdruck kompatibel, während () -> {} oder () -> { return; } void kompatibel sind. Beachten Sie, dass () -> { for(;;); } und () -> { throw new RuntimeException(); } beide, void kompatibel und wertkompatibel sind, da sie normalerweise nicht abgeschlossen werden.

Die Form (arg) -> expression ist wertkompatibel, wenn der Ausdruck einen Wert ergibt. Aber es gibt auch Ausdrücke, die Anweisungen gleichzeitig sind. Diese Ausdrücke können einen Nebeneffekt haben und können daher als alleinstehende Anweisung geschrieben werden, um nur den Nebeneffekt zu erzeugen, wobei das erzeugte Ergebnis ignoriert wird. In ähnlicher Weise kann die Form (arg) -> expression void kompatibel sein, wenn der Ausdruck auch eine Anweisung ist.

Ein Ausdruck der Form s -> s kann nicht void kompatibel sein, da s keine Anweisung ist, d. h. Sie können auch nicht s -> { s; } schreiben. Andererseits kann s -> s.toString() void kompatibel sein, da Methodenaufrufe Anweisungen sind. Entsprechend kann s -> i++ void kompatibel sein, da Inkremente als Anweisung verwendet werden können, also ist auch s -> { i++; } gültig. Natürlich muss i ein Feld sein, damit dies funktioniert, keine lokale Variable.

Die Java-Sprachspezifikation §14.8. Ausdruckstatements listet alle Ausdrücke auf, die als Anweisungen verwendet werden können. Neben den bereits erwähnten Methodenaufrufen und Inkrementierungs- / Dekrementoperatoren benennt er Zuordnungen und Klasseninstanzerstellungsausdrücke, so dass auch s -> foo=s und s -> new WhatEver(s) void kompatibel sind.

Als eine Randnotiz ist die Form (arg) -> methodReturningVoid(arg) die einzige Ausdrucksform, die nicht wertkompatibel ist.

    
Holger 19.05.2016 09:03
quelle

Tags und Links