Argumento de Java 8 lambda Void

188

Digamos que tengo la siguiente interfaz funcional en Java 8:

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

Y para algunos casos necesito una acción sin argumentos o tipo de retorno. Entonces escribo algo como esto:

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

Sin embargo, me da un error de compilación, necesito escribirlo como

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

Lo cual es feo. ¿Hay alguna forma de deshacerse del Voidparámetro type?

Wickoo
fuente
1
Eche un vistazo a stackoverflow.com/questions/14319787/…
BobTheBuilder
77
Si necesita una acción, como la definió, no es posible. Sin embargo, su primer ejemplo podría encajar en Runnablelo que está buscandoRunnable r = () -> System.out.println("Do nothing!");
Alexis C.
1
@BobTheBuilder No quiero usar un consumidor como se sugiere en esa publicación.
Wickoo
2
La respuesta de Matt hace que los tipos funcionen, pero ¿qué hace la persona que llama cuando obtiene un valor de retorno nulo?
Stuart Marks
8
¡Podría cruzar los dedos y esperar que las sugerencias 2 y 3 en esta publicación sean aceptadas para Java 9!
assylias

Respuestas:

110

La sintaxis que busca es posible con una pequeña función auxiliar que convierte a Runnableen Action<Void, Void>(puede colocarla, Actionpor ejemplo):

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"));
Mate
fuente
44
Esta es la solución más limpia que puede obtener, IMO, por lo que +1 (o con un método estático en la interfaz en sí)
Alexis C.
La solución de Konstantin Yovkov a continuación (con @FunctionalInterface) es una mejor solución, ya que no involucra genéricos y no requiere código adicional.
uthomas
@uthomas Lo siento, no veo una respuesta que involucre @FunctionalInterface. Simplemente dice que no es posible extenderlo ...
Matt
1
Hola @ Matt, lo siento. Reaccioné demasiado rápido. Para la pregunta dada, su respuesta es perfectamente válida. Lamentablemente, mi voto está bloqueado, por lo que no puedo eliminar mi -1 en esta respuesta. Dos notas: 1. En lugar de Runnabletomar una acción personalizada, debería @FunctionalInterfacellamarse algo SideEffect2. La necesidad de dicha función auxiliar resalta que algo extraño está sucediendo y probablemente la abstracción está rota.
uthomas
531

Úselo Suppliersi no toma nada, pero devuelve algo.

Úselo Consumersi toma algo, pero no devuelve nada.

Úselo Callablesi devuelve un resultado y podría arrojar (más parecido Thunken términos generales de CS).

Úselo Runnablesi no lo hace y no puede lanzar.

x1a0
fuente
Como un ejemplo que hice esto para envolver llamada de vuelta "vacío": public static void wrapCall(Runnable r) { r.run(); }. Gracias
Maxence
13
Hermosa respuesta. Corto y preciso.
Clint Eastwood
Desafortunadamente, no ayuda si debe lanzar una excepción marcada.
Jesse Glick el
13
Como finalización de esta respuesta, que no valdría la pena editar: también puede usar BiConsumer (toma 2, devuelve 0), Función (toma 1, devuelve 1) y BiFunction (toma 2, devuelve 1). Esos son los más importantes para saber
CLOVIS
2
¿Hay algo como Callable (que arroja una excepción en su método call ()) pero requiere algún valor de retorno?
dpelisek
40

La lambda:

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

en realidad representa una implementación para una interfaz como:

public interface Something {
    void action();
}

que es completamente diferente al que has definido. Es por eso que obtienes un error.

Como no puede extender su @FunctionalInterface, ni introducir uno nuevo, creo que no tiene muchas opciones. Sin Optional<T>embargo, puede usar las interfaces para denotar que faltan algunos de los valores (tipo de retorno o parámetro de método). Sin embargo, esto no hará que el cuerpo lambda sea más simple.

Konstantin Yovkov
fuente
El problema es que su Somethingfunción no puede ser un subtipo de mi Actiontipo y no puedo tener dos tipos diferentes.
Wickoo
Técnicamente puede, pero dijo que quiere evitarlo. :)
Konstantin Yovkov
31

Puede crear una subinterfaz para ese caso especial:

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

Utiliza un método predeterminado para anular el método parametrizado heredado Void execute(Void), delegando la llamada al método más simple void execute().

El resultado es que es mucho más simple de usar:

Command c = () -> System.out.println("Do nothing!");
Jordão
fuente
¿De dónde viene esta acción <Void, Void>? ¿Ni las interfaces Swing ni JAX-WX Action tienen una interfaz tan genérica?
luis.espinal
1
@ luis.espinal: Action<T, U>se declara en la pregunta .....
Jordão
Jajaja, ¿cómo diablos me perdí eso? ¡Gracias!
luis.espinal
6

Creo que esta tabla es corta y útil:

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

Como se dijo en las otras respuestas, la opción adecuada para este problema es un Runnable

GabrielRado
fuente
5

Eso no es posible. Una función que tiene un tipo de retorno no nulo (incluso si es Void) tiene que devolver un valor. Sin embargo, podría agregar métodos estáticos Actionque le permitan "crear" a 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;
   } 
}

Eso te permitiría escribir lo siguiente:

// 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
fuente
4

Agregue un método estático dentro de su interfaz funcional

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

}

Salida

Do nothing!
MCHAppy
fuente
3

No creo que sea posible, porque las definiciones de funciones no coinciden en su ejemplo.

Su expresión lambda se evalúa exactamente como

void action() { }

mientras que su declaración parece

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

como ejemplo, si tiene la siguiente interfaz

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

el único tipo de función (durante la creación de instancias) que será compatible parece

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

y la falta de declaración o argumento de devolución le dará un error de compilación.

Por lo tanto, si declara una función que toma un argumento y devuelve uno, creo que es imposible convertirlo en una función que no menciona ninguno de los anteriores.

pnadczuk
fuente
3

Solo como referencia, qué interfaz funcional se puede usar para referencia de método en casos en los que el método arroja y / o devuelve un valor.

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
fuente
Por favor agregue un poco más de contexto. Esto parece interesante, pero su significado no es inmediatamente obvio.
bnieland