Argomento Java 8 lambda Void


188

Diciamo che ho la seguente interfaccia funzionale in Java 8:

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

E per alcuni casi ho bisogno di un'azione senza argomenti o tipo di ritorno. Quindi scrivo qualcosa del genere:

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

Tuttavia, mi dà errore di compilazione, devo scriverlo come

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

Che è brutto. C'è un modo per sbarazzarsi del Voidparametro type?



7
Se hai bisogno di un'azione, come l'hai definita, non è possibile. Tuttavia, il tuo primo esempio potrebbe rientrare in a Runnable, quello che stai cercandoRunnable r = () -> System.out.println("Do nothing!");
Alexis C.

1
@BobTheBuilder Non voglio usare un consumatore come suggerito in quel post.
Wickoo,

2
La risposta di Matt fa funzionare i tipi, ma cosa fa il chiamante quando ottiene un valore di ritorno nullo?
Stuart segna il

8
Potresti incrociare le dita e sperare che i suggerimenti 2 e 3 in questo post vengano accettati per Java 9!
Assylias,

Risposte:


110

La sintassi che stai cercando è possibile con una piccola funzione di aiuto che converte un Runnablein Action<Void, Void>(puoi inserirlo Actionad esempio):

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

4
Questa è la soluzione più pulita che potresti ottenere, IMO, quindi +1 (o con un metodo statico nell'interfaccia stessa)
Alexis C.

La soluzione di Konstantin Yovkov di seguito (con @FunctionalInterface) è una soluzione migliore, perché non coinvolge generici e non richiede codice aggiuntivo.
uthomas,

@uthomas Spiacente, non vedo una risposta che coinvolga @FunctionalInterface. Dice semplicemente che non è possibile estenderlo ...
Matt,

1
Ciao @Matt, scusa. Ho reagito troppo in fretta. Per la domanda a cui rispondi è perfettamente valido. Sfortunatamente, il mio voto è bloccato, quindi non posso rimuovere il mio -1 da questa risposta. Due note: 1. Invece di Runnableagire dovrebbe prendere @FunctionalInterfacequalcosa di personalizzato chiamato SideEffect, 2. la necessità di tale funzione di supporto evidenzia che sta succedendo qualcosa di strano e probabilmente l'astrazione è rotta.
uthomas,

531

Utilizzare Supplierse non serve nulla, ma restituisce qualcosa.

Utilizzare Consumerse richiede qualcosa, ma non restituisce nulla.

Utilizzare Callablese restituisce un risultato e potrebbe essere lanciato (più simile a Thunkin termini CS generali).

Usa Runnablese non fa né e non può lanciare.


Per fare un esempio ho fatto questo chiamata di ritorno per avvolgere "vuoto": public static void wrapCall(Runnable r) { r.run(); }. Grazie
Maxence,

13
bella risposta. Corto e preciso
Clint Eastwood,

Sfortunatamente, non aiuta se deve generare un'eccezione controllata.
Jesse Glick,

13
Come completamento per questa risposta, che non varrebbe una modifica: puoi anche usare BiConsumer (richiede 2, restituisce 0), Funzione (richiede 1, restituisce 1) e BiFunction (richiede 2, restituisce 1). Quelli sono i più importanti da sapere
CLOVIS

2
Esiste qualcosa come Callable (che genera un'eccezione nel suo metodo call ()) ma richiede un valore di ritorno?
Dpelisek,

40

La lambda:

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

in realtà rappresenta un'implementazione per un'interfaccia come:

public interface Something {
    void action();
}

che è completamente diverso da quello che hai definito. Ecco perché ricevi un errore.

Dal momento che non puoi ampliare il tuo @FunctionalInterface, né introdurne uno nuovo, quindi penso che non hai molte opzioni. Optional<T>Tuttavia, è possibile utilizzare le interfacce per indicare che mancano alcuni valori (tipo restituito o parametro del metodo). Tuttavia, questo non renderà il corpo lambda più semplice.


Il problema è che la tua Somethingfunzione non può essere un sottotipo del mio Actiontipo e non posso avere due tipi diversi.
Wickoo,

Tecnicamente può, ma ha detto che vuole evitarlo. :)
Konstantin Yovkov

31

Puoi creare una sotto-interfaccia per quel caso speciale:

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

Utilizza un metodo predefinito per sovrascrivere il metodo con parametri ereditato Void execute(Void), delegando la chiamata al metodo più semplice void execute().

Il risultato è che è molto più semplice da usare:

Command c = () -> System.out.println("Do nothing!");

Da dove viene questa azione <Void, Void>? Né le interfacce Swing né JAX-WX Action hanno un'interfaccia così generica?
luis.espinal,

1
@ luis.espinal: Action<T, U>è dichiarato nella domanda .....
Jordão,

Hahaha, che diamine mi è mancato? Grazie!
luis.espinal,

6

Penso che questa tabella sia breve e utile:

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

Come detto nelle altre risposte, l'opzione appropriata per questo problema è a Runnable


5

Non è possibile. Una funzione che ha un tipo di ritorno non vuoto (anche se è Void) deve restituire un valore. Tuttavia, è possibile aggiungere metodi statici Actionche consentono di "creare" un 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;
   } 
}

Ciò ti consentirebbe di scrivere quanto segue:

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

4

Aggiungi un metodo statico all'interno della tua interfaccia funzionale

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

}

Produzione

Do nothing!

3

Non credo sia possibile, perché le definizioni delle funzioni non corrispondono nel tuo esempio.

La tua espressione lambda viene valutata esattamente come

void action() { }

mentre la tua dichiarazione sembra

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

ad esempio, se si dispone della seguente interfaccia

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

l'unico tipo di funzione (durante l'istanza) che sarà compatibile è simile

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

e la mancanza di dichiarazione di ritorno o argomento ti darà un errore del compilatore.

Pertanto, se dichiari una funzione che accetta un argomento e ne restituisce uno, penso che sia impossibile convertirlo in una funzione che non ha nessuno dei precedenti.


3

Solo per riferimento quale interfaccia funzionale può essere utilizzata per riferimento al metodo nei casi in cui il metodo genera e / o restituisce un valore.

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
}

Aggiungi un po 'più di contesto. Sembra interessante, ma il suo significato non è immediatamente evidente.
bnieland,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.