Ich spiele mit Java 8 herum, um herauszufinden, wie es als Bürger erster Klasse funktioniert. Ich habe folgenden Ausschnitt:
package test;
import java.util.*;
import java.util.function.*;
public class Test {
public static void myForEach(List<Integer> list, Function<Integer, Void> myFunction) {
list.forEach(functionToBlock(myFunction));
}
public static void displayInt(Integer i) {
System.out.println(i);
}
public static void main(String[] args) {
List<Integer> theList = new ArrayList<>();
theList.add(1);
theList.add(2);
theList.add(3);
theList.add(4);
theList.add(5);
theList.add(6);
myForEach(theList, Test::displayInt);
}
}
Was ich versuche, ist die Pass-Methode displayInt
zur Methode myForEach
Verwenden einer Methodenreferenz. Der Compiler erzeugt den folgenden Fehler:
src/test/Test.java:9: error: cannot find symbol
list.forEach(functionToBlock(myFunction));
^
symbol: method functionToBlock(Function<Integer,Void>)
location: class Test
src/test/Test.java:25: error: method myForEach in class Test cannot be applied to given ty
pes;
myForEach(theList, Test::displayInt);
^
required: List<Integer>,Function<Integer,Void>
found: List<Integer>,Test::displayInt
reason: argument mismatch; bad return type in method reference
void cannot be converted to Void
Der Compiler beschwert sich darüber void cannot be converted to Void
. Ich weiß nicht, wie ich den Typ der Funktionsschnittstelle in der Signatur von angeben soll myForEach
damit der Code kompiliert. Ich weiß, ich könnte einfach den Rückgabetyp von ändern displayInt
zu Void
und dann zurück null
. Es kann jedoch Situationen geben, in denen es nicht möglich ist, die Methode, die ich übergeben möchte, an anderer Stelle zu ändern. Gibt es eine einfache Möglichkeit zur Wiederverwendung? displayInt
wie es ist?
Sie versuchen, den falschen Schnittstellentyp zu verwenden. Der Typ Funktion ist in diesem Fall nicht geeignet, da es einen Parameter erhält und einen Rückgabewert hat. Stattdessen sollten Sie verwenden Verbraucher (früher als Block bekannt)
Der Funktionstyp wird als deklariert
interface Function<T,R> {
R apply(T t);
}
Der Consumer-Typ ist jedoch kompatibel mit dem, wonach Sie suchen:
interface Consumer<T> {
void accept(T t);
}
Daher ist Consumer mit Methoden kompatibel, die ein T empfangen und nichts zurückgeben (void). Und das ist, was Sie wollen.
Wenn ich zum Beispiel alle Elemente in einer Liste anzeigen möchte, könnte ich einfach einen Verbraucher dafür mit einem Lambda-Ausdruck erstellen:
List<String> allJedi = asList("Luke","Obiwan","Quigon");
allJedi.forEach( jedi -> System.out.println(jedi) );
Sie können oben sehen, dass in diesem Fall der Lambda-Ausdruck einen Parameter erhält und keinen Rückgabewert hat.
Wenn ich nun anstelle eines Lambda-Ausdrucks eine Methodenreferenz verwenden möchte, um einen Verbrauch dieses Typs zu erstellen, brauche ich eine Methode, die einen String empfängt und void zurückgibt, richtig?.
Ich könnte verschiedene Arten von Methodenreferenzen verwenden, aber in diesem Fall nutzen wir eine Objektmethodenreferenz, indem wir die verwenden println
Methode in der System.out
Objekt, etwa so:
Consumer<String> block = System.out::println
Oder ich könnte es einfach tun
allJedi.forEach(System.out::println);
Das println
-Methode ist geeignet, da sie einen Wert empfängt und einen Rückgabetyp void hat, genau wie die accept
Methode im Consumer.
In Ihrem Code müssen Sie also Ihre Methodensignatur in etwa so ändern:
public static void myForEach(List<Integer> list, Consumer<Integer> myBlock) {
list.forEach(myBlock);
}
Und dann sollten Sie in Ihrem Fall in der Lage sein, einen Verbraucher mit einer statischen Methodenreferenz zu erstellen, indem Sie Folgendes tun:
myForEach(theList, Test::displayInt);
Letztendlich könntest du deine sogar loswerden myForEach
Methode zusammen und tun Sie einfach:
theList.forEach(Test::displayInt);
Über Funktionen als Bürger erster Klasse
Alles in allem ist die Wahrheit, dass Java 8 keine Funktionen als erstklassige Bürger haben wird, da der Sprache kein struktureller Funktionstyp hinzugefügt wird. Java wird einfach eine alternative Möglichkeit bieten, Implementierungen funktionaler Schnittstellen aus Lambda-Ausdrücken und Methodenreferenzen zu erstellen. Letztendlich werden Lambda-Ausdrücke und Methodenreferenzen an Objektreferenzen gebunden, daher haben wir nur Objekte als erstklassige Bürger. Wichtig ist, dass die Funktionalität vorhanden ist, da wir Objekte als Parameter übergeben, sie an Variablenreferenzen binden und sie als Werte von anderen Methoden zurückgeben können, dann dienen sie ziemlich genau einem ähnlichen Zweck.
Wenn Sie eine Funktion als Argument akzeptieren müssen, die keine Argumente akzeptiert und kein Ergebnis zurückgibt (void), ist es meiner Meinung nach immer noch am besten, so etwas zu haben
public interface Thunk { void apply(); }
irgendwo in deinem Code. In meinen Kursen zur funktionalen Programmierung wurde das Wort „thunk“ verwendet, um solche Funktionen zu beschreiben. Warum es nicht in java.util.function ist, entzieht sich meinem Verständnis.
In anderen Fällen stelle ich fest, dass selbst wenn java.util.function etwas hat, das mit der von mir gewünschten Signatur übereinstimmt, es sich immer noch nicht immer richtig anfühlt, wenn die Benennung der Schnittstelle nicht mit der Verwendung der Funktion in meinem Code übereinstimmt. Ich denke, es ist ein ähnlicher Punkt, der hier an anderer Stelle in Bezug auf ‘Runnable’ gemacht wird – ein Begriff, der mit der Thread-Klasse verbunden ist – also, obwohl es die Signatur hat, die ich brauche, ist es immer noch wahrscheinlich, dass es den Leser verwirrt.
Setzen Sie den Rückgabetyp auf Void
Anstatt von void
und return null
// Modify existing method
public static Void displayInt(Integer i) {
System.out.println(i);
return null;
}
ODER
// Or use Lambda
myForEach(theList, i -> {System.out.println(i);return null;});