Wie werden Funktionstypen für void (nicht Void) Methoden in Java8 angegeben?

143

Ich spiele mit Java 8 herum, um herauszufinden, wie es als erstklassige Bürger funktioniert. Ich habe das folgende Snippet:

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);
    }
}

Ich versuche, Methode displayIntan Methode unter myForEachVerwendung einer Methodenreferenz zu übergeben. 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 angeben soll myForEach, damit der Code kompiliert wird. Ich weiß , ich könnte einfach den Rückgabetyp ändern von displayIntzu Voidund dann zurück null. Es kann jedoch Situationen geben, in denen es nicht möglich ist, die Methode zu ändern, die ich an eine andere Stelle übergeben möchte. Gibt es eine einfache Möglichkeit zur Wiederverwendung displayInt?

Valentin
quelle

Antworten:

244

Sie versuchen, den falschen Schnittstellentyp zu verwenden. Der Typ Function ist in diesem Fall nicht geeignet, da er einen Parameter empfängt und einen Rückgabewert hat. Verwenden Sie stattdessen Consumer (früher als Block bekannt).

Der Funktionstyp wird als deklariert

interface Function<T,R> {
  R apply(T t);
}

Der Verbrauchertyp ist jedoch mit dem gesuchten kompatibel:

interface Consumer<T> {
   void accept(T t);
}

Daher ist Consumer mit Methoden kompatibel, die ein T erhalten und nichts zurückgeben (void). Und das ist was du willst.

Wenn ich beispielsweise alle Elemente in einer Liste anzeigen möchte, kann ich einfach einen Consumer 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 empfängt und keinen Rückgabewert hat.

Wenn ich eine Methodenreferenz anstelle eines Lambda-Ausdrucks verwenden möchte, um einen Verbrauch dieses Typs zu erstellen, benötige ich eine Methode, die einen String empfängt und void zurückgibt, oder?

Ich könnte verschiedene Arten von Methodenreferenzen verwenden, aber in diesem Fall nutzen wir eine Objektmethodenreferenz, indem wir die printlnMethode im System.outObjekt wie folgt verwenden:

Consumer<String> block = System.out::println

Oder ich könnte es einfach tun

allJedi.forEach(System.out::println);

Die printlnMethode ist geeignet, da sie einen Wert empfängt und den Rückgabetyp void hat, genau wie die acceptMethode in Consumer.

In Ihrem Code müssen Sie also Ihre Methodensignatur wie folgt ä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 mithilfe einer statischen Methodenreferenz zu erstellen, indem Sie Folgendes tun:

myForEach(theList, Test::displayInt);

Letztendlich könnten Sie sogar Ihre myForEachMethode ganz loswerden und einfach Folgendes tun:

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 bietet einfach eine alternative Möglichkeit, Implementierungen von Funktionsschnittstellen aus Lambda-Ausdrücken und Methodenreferenzen zu erstellen. Letztendlich werden Lambda-Ausdrücke und Methodenreferenzen an Objektreferenzen gebunden sein, daher haben wir nur Objekte als erstklassige Bürger. Wichtig ist, dass die Funktionalität vorhanden ist, da wir Objekte als Parameter übergeben, an Variablenreferenzen binden und als Werte von anderen Methoden zurückgeben können. Dann dienen sie so ziemlich einem ähnlichen Zweck.

Edwin Dalorzo
quelle
1
Übrigens Blockwurde für Consumerin den neuesten Versionen der JDK 8 API geändert
Edwin Dalorzo
4
Der letzte Absatz beschönigt einige wichtige Fakten: Lambda-Ausdrücke und Methodenreferenzen sind mehr als eine syntaktische Ergänzung. Die JVM selbst bietet neue Typen, um Methodenreferenzen effizienter zu handhaben als bei anonymen Klassen (am wichtigsten MethodHandle). Auf diese Weise kann der Java-Compiler effizienteren Code erstellen, z. B. Lambdas ohne Abschluss als statische Methoden implementieren und so die Generierung von Methoden verhindern zusätzliche Klassen.
Feuermurmel
7
Und die richtige Version von Function<Void, Void>ist Runnable.
OrangeDog
2
@OrangeDog Dies ist nicht ganz richtig. In den Kommentaren zu Runnableund CallableSchnittstellen steht geschrieben, dass sie in Verbindung mit Threads verwendet werden sollen . Die ärgerliche Folge davon ist, dass sich einige statische Analysewerkzeuge (wie Sonar) beschweren, wenn Sie die run()Methode direkt aufrufen .
Denis
Ich komme ausschließlich aus Java und frage mich, in welchen Fällen Java Funktionen auch nach der Einführung funktionaler Schnittstellen und Lambdas seit Java8 nicht als erstklassige Bürger behandeln kann.
Vivek Sethi
21

Wenn Sie eine Funktion als Argument akzeptieren müssen, das 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 funktionalen Programmierkursen wurde das Wort "Thunk" verwendet, um solche Funktionen zu beschreiben. Warum es nicht in java.util.function ist, kann ich nicht verstehen.

In anderen Fällen stelle ich fest, dass selbst wenn java.util.function etwas hat, das mit der gewünschten Signatur übereinstimmt, es sich immer noch nicht 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 an anderer Stelle in Bezug auf 'Runnable' angesprochen wird - ein Begriff, der mit der Thread-Klasse assoziiert ist -, obwohl er möglicherweise die Signatur hat, die ich brauche, ist es dennoch wahrscheinlich, dass er den Leser verwirrt.

LOAS
quelle
Insbesondere sind Runnablekeine geprüften Ausnahmen zulässig, sodass sie für viele Anwendungen unbrauchbar sind. Da dies auch Callable<Void>nicht nützlich ist, müssen Sie anscheinend Ihre eigenen definieren. Hässlich.
Jesse Glick
In Bezug auf das überprüfte Ausnahmeproblem haben die neuen Funktionen von Java im Allgemeinen damit zu kämpfen. Es ist ein riesiger Blocker für einige Dinge wie die Stream-API. Sehr schlechtes Design von ihrer Seite.
user2223059
0

Setzen Sie den Rückgabetyp auf Voidanstelle von voidundreturn 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;});
Dojo
quelle
-2

Ich denke, Sie sollten stattdessen die Consumer-Oberfläche verwenden Function<T, R>.

Ein Consumer ist im Grunde eine funktionale Schnittstelle, die einen Wert akzeptiert und nichts zurückgibt (dh nichtig).

In Ihrem Fall können Sie einen Verbraucher an einer anderen Stelle in Ihrem Code wie folgt erstellen:

Consumer<Integer> myFunction = x -> {
    System.out.println("processing value: " + x);    
    .... do some more things with "x" which returns nothing...
}

Dann können Sie Ihren myForEachCode durch das folgende Snippet ersetzen :

public static void myForEach(List<Integer> list, Consumer<Integer> myFunction) 
{
  list.forEach(x->myFunction.accept(x));
}

Sie behandeln myFunction als erstklassiges Objekt.

Anthony Anyanwu
quelle
2
Was fügt dies über die vorhandenen Antworten hinaus hinzu?
Matthew Read