Java 8 Lambda Void Argument

186

Angenommen, ich habe die folgende Funktionsschnittstelle in Java 8:

interface Action<T, U> {
   U execute(T t);
}

Und für einige Fälle benötige ich eine Aktion ohne Argumente oder Rückgabetyp. Also schreibe ich so etwas:

Action<Void, Void> a = () -> { System.out.println("Do nothing!"); };

Es gibt mir jedoch Kompilierungsfehler, ich muss es schreiben als

Action<Void, Void> a = (Void v) -> { System.out.println("Do nothing!"); return null;};

Welches ist hässlich. Gibt es eine Möglichkeit, den VoidTypparameter loszuwerden ?

Wickoo
quelle
1
Werfen
BobTheBuilder
7
Wenn Sie eine Aktion benötigen, wie Sie sie definiert haben, ist dies nicht möglich. Ihr erstes Beispiel könnte jedoch in ein passen Runnable, wonach Sie suchenRunnable r = () -> System.out.println("Do nothing!");
Alexis C.
1
@ BobTheBuilder Ich möchte keinen Consumer verwenden, wie in diesem Beitrag vorgeschlagen.
Wickoo
2
Durch die Antwort von Matt funktionieren die Typen, aber was macht der Anrufer, wenn er einen Null-Rückgabewert erhält?
Stuart Marks
8
Sie könnten die Daumen drücken und hoffen, dass die Vorschläge 2 und 3 in diesem Beitrag für Java 9 akzeptiert werden!
Assylias

Antworten:

109

Die Syntax, nach der Sie suchen, ist mit einer kleinen Hilfsfunktion möglich, die a Runnablein konvertiert Action<Void, Void>(Sie können sie Actionbeispielsweise einfügen):

public static Action<Void, Void> action(Runnable runnable) {
    return (v) -> {
        runnable.run();
        return null;
    };
}

// Somewhere else in your code
 Action<Void, Void> action = action(() -> System.out.println("foo"));
Matt
quelle
4
Dies ist die sauberste Problemumgehung, die Sie bekommen könnten, IMO, also +1 (oder mit einer statischen Methode in der Schnittstelle selbst)
Alexis C.
Die unten stehende Lösung von Konstantin Yovkov (mit @FunctionalInterface) ist eine bessere Lösung, da sie keine Generika enthält und keinen zusätzlichen Code erfordert.
Uthomas
@uthomas Sorry, ich sehe keine Antwort mit @FunctionalInterface. Er sagt nur, dass es nicht möglich ist, es zu verlängern ...
Matt
1
Hallo @Matt, sorry. Ich habe zu schnell reagiert. Für die gegebene Frage, die Sie beantworten, ist vollkommen gültig. Leider ist meine Stimme gesperrt, so dass ich meine -1 bei dieser Antwort nicht entfernen kann. Zwei Anmerkungen: 1. Anstelle einer RunnableAktion sollte ein benutzerdefinierter @FunctionalInterfaceVorgang ausgeführt werden. SideEffect2. Die Notwendigkeit einer solchen Hilfsfunktion zeigt, dass etwas Seltsames vor sich geht und wahrscheinlich die Abstraktion unterbrochen ist.
Uthomas
526

Verwenden SupplierSie diese Option, wenn nichts benötigt wird, aber etwas zurückgegeben wird.

Verwenden ConsumerSie diese Option, wenn etwas benötigt wird, aber nichts zurückgegeben wird.

Verwenden CallableSie diese Option, wenn ein Ergebnis zurückgegeben wird und möglicherweise ausgelöst wird ( Thunkim Allgemeinen ähnlich wie bei CS).

Verwenden Sie, Runnablewenn es beides nicht tut und nicht werfen kann.

x1a0
quelle
Als Beispiel habe ich dies getan, um den Rückruf "void" zu beenden : public static void wrapCall(Runnable r) { r.run(); }. Vielen Dank
Maxence
12
schöne Antwort. Kurz und präzise.
Clint Eastwood
Hilft nicht, wenn es leider eine aktivierte Ausnahme auslösen muss.
Jesse Glick
11
Als Abschluss für diese Antwort, die keine Bearbeitung wert wäre: Sie können auch BiConsumer (nimmt 2, gibt 0 zurück), Function (nimmt 1, gibt 1 zurück) und BiFunction (nimmt 2, gibt 1 zurück) verwenden. Das sind die wichtigsten zu wissen
CLOVIS
2
Gibt es so etwas wie Callable (das in seiner call () -Methode eine Ausnahme auslöst), aber einen Rückgabewert erfordert?
Dpelisek
40

Das Lambda:

() -> { System.out.println("Do nothing!"); };

stellt tatsächlich eine Implementierung für eine Schnittstelle dar, wie:

public interface Something {
    void action();
}

Das ist völlig anders als die, die Sie definiert haben. Deshalb erhalten Sie eine Fehlermeldung.

Da Sie Ihre nicht erweitern @FunctionalInterfaceoder eine brandneue vorstellen können, haben Sie meiner Meinung nach nicht viele Optionen. Sie können die Optional<T>Schnittstellen verwenden, um anzuzeigen, dass einige der Werte (Rückgabetyp oder Methodenparameter) fehlen. Dies wird den Lambda-Körper jedoch nicht einfacher machen.

Konstantin Yovkov
quelle
Das Problem ist, dass Ihre SomethingFunktion kein Subtyp meines ActionTyps sein kann und ich keine zwei verschiedenen Typen haben kann.
Wickoo
Technisch kann er, aber er sagte, er will es vermeiden. :)
Konstantin Yovkov
31

Sie können eine Unterschnittstelle für diesen speziellen Fall erstellen:

interface Command extends Action<Void, Void> {
  default Void execute(Void v) {
    execute();
    return null;
  }
  void execute();
}

Es verwendet eine Standardmethode , um die geerbte parametrisierte Methode zu überschreiben Void execute(Void)und den Aufruf an die einfachere Methode zu delegieren void execute().

Das Ergebnis ist, dass es viel einfacher zu bedienen ist:

Command c = () -> System.out.println("Do nothing!");
Jordão
quelle
5

Das ist nicht möglich. Eine Funktion mit einem nicht ungültigen Rückgabetyp (auch wenn dies der Fall ist Void) muss einen Wert zurückgeben. Sie können jedoch statische Methoden hinzufügen Action, mit denen Sie Folgendes erstellen können Action:

interface Action<T, U> {
   U execute(T t);

   public static Action<Void, Void> create(Runnable r) {
       return (t) -> {r.run(); return null;};
   }

   public static <T, U> Action<T, U> create(Action<T, U> action) {
       return action;
   } 
}

Damit können Sie Folgendes schreiben:

// create action from Runnable
Action.create(()-> System.out.println("Hello World")).execute(null);
// create normal action
System.out.println(Action.create((Integer i) -> "number: " + i).execute(100));
Fabian
quelle
5

Ich denke, diese Tabelle ist kurz und nützlich:

Supplier       ()    -> x
Consumer       x     -> ()
Callable       ()    -> x throws ex
Runnable       ()    -> ()
Function       x     -> y
BiFunction     x,y   -> z
Predicate      x     -> boolean
UnaryOperator  x1    -> x2
BinaryOperator x1,x2 -> x3

Wie in den anderen Antworten erwähnt, ist die geeignete Option für dieses Problem a Runnable

GabrielRado
quelle
4

Fügen Sie eine statische Methode in Ihre Funktionsschnittstelle ein

package example;

interface Action<T, U> {
       U execute(T t);
       static  Action<Void,Void> invoke(Runnable runnable){
           return (v) -> {
               runnable.run();
                return null;
            };         
       }
    }

public class Lambda {


    public static void main(String[] args) {

        Action<Void, Void> a = Action.invoke(() -> System.out.println("Do nothing!"));
        Void t = null;
        a.execute(t);
    }

}

Ausgabe

Do nothing!
MCHAppy
quelle
3

Ich denke nicht, dass dies möglich ist, da die Funktionsdefinitionen in Ihrem Beispiel nicht übereinstimmen.

Ihr Lambda-Ausdruck wird genau wie ausgewertet

void action() { }

während Ihre Erklärung aussieht wie

Void action(Void v) {
    //must return Void type.
}

Als Beispiel, wenn Sie folgende Schnittstelle haben

public interface VoidInterface {
    public Void action(Void v);
}

Die einzige Art von Funktion (während der Instanziierung), die kompatibel ist, sieht aus

new VoidInterface() {
    public Void action(Void v) {
        //do something
        return v;
    }
}

Wenn Sie keine return-Anweisung oder kein Argument haben, erhalten Sie einen Compilerfehler.

Wenn Sie also eine Funktion deklarieren, die ein Argument akzeptiert und eines zurückgibt, ist es meines Erachtens unmöglich, sie in eine Funktion umzuwandeln, die keine der oben genannten Funktionen ausführt.

pnadczuk
quelle
3

Nur als Referenz, welche Funktionsschnittstelle als Methodenreferenz verwendet werden kann, wenn die Methode einen Wert auslöst und / oder zurückgibt.

void notReturnsNotThrows() {};
void notReturnsThrows() throws Exception {}
String returnsNotThrows() { return ""; }
String returnsThrows() throws Exception { return ""; }

{
    Runnable r1 = this::notReturnsNotThrows; //ok
    Runnable r2 = this::notReturnsThrows; //error
    Runnable r3 = this::returnsNotThrows; //ok
    Runnable r4 = this::returnsThrows; //error

    Callable c1 = this::notReturnsNotThrows; //error
    Callable c2 = this::notReturnsThrows; //error
    Callable c3 = this::returnsNotThrows; //ok
    Callable c4 = this::returnsThrows; //ok

}


interface VoidCallableExtendsCallable extends Callable<Void> {
    @Override
    Void call() throws Exception;
}

interface VoidCallable {
    void call() throws Exception;
}

{
    VoidCallableExtendsCallable vcec1 = this::notReturnsNotThrows; //error
    VoidCallableExtendsCallable vcec2 = this::notReturnsThrows; //error
    VoidCallableExtendsCallable vcec3 = this::returnsNotThrows; //error
    VoidCallableExtendsCallable vcec4 = this::returnsThrows; //error

    VoidCallable vc1 = this::notReturnsNotThrows; //ok
    VoidCallable vc2 = this::notReturnsThrows; //ok
    VoidCallable vc3 = this::returnsNotThrows; //ok
    VoidCallable vc4 = this::returnsThrows; //ok
}
SlavaL
quelle
Bitte fügen Sie etwas mehr Kontext hinzu. Das sieht interessant aus, aber seine Bedeutung ist nicht sofort offensichtlich.
Bnieland