Argumento Java 8 lambda Void

188

Digamos que eu tenha a seguinte interface funcional no Java 8:

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

E, em alguns casos, preciso de uma ação sem argumentos ou tipo de retorno. Então eu escrevo algo assim:

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

No entanto, isso me dá erro de compilação, preciso escrevê-lo como

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

O que é feio. Existe alguma maneira de se livrar do Voidparâmetro type?

Wickoo
fonte
1
Dê uma olhada em stackoverflow.com/questions/14319787/…
BobTheBuilder
7
Se você precisar de uma ação, como a definiu, não será possível. No entanto, seu primeiro exemplo pode caber em um Runnable, o que você está procurando #Runnable r = () -> System.out.println("Do nothing!");
Alexis C.
1
@BobTheBuilder Não quero usar um Consumidor, conforme sugerido nesse post.
Wickoo
2
A resposta de Matt faz os tipos funcionarem, mas o que o chamador faz quando obtém um valor de retorno nulo?
Stuart Marks
8
Você pode cruzar os dedos e esperar que as sugestões 2 e 3 deste post sejam aceitas para o Java 9!
Assilias

Respostas:

110

A sintaxe que você procura é possível com uma pequena função auxiliar que converte um Runnableem Action<Void, Void>(você pode colocá-lo, Actionpor exemplo):

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
fonte
4
Este é o mais limpo solução alternativa que você poderia começar, IMO, de modo +1 (ou com um método estático na própria interface)
Alexis C.
A solução de Konstantin Yovkov abaixo (com @FunctionalInterface) é uma solução melhor, porque não envolve genéricos e não requer código extra.
Uthomas
@uthomas Desculpe, não vejo uma resposta envolvente @FunctionalInterface. Ele simplesmente diz que não é possível estendê-lo ...
Matt
1
Oi @ Matt, desculpe. Eu reagi rápido demais. Para a pergunta dada, você responde é perfeitamente válido. Infelizmente, meu voto está bloqueado, portanto não consigo remover meu -1 nesta resposta. Duas notas: 1. Em vez de Runnableação, deve-se adotar @FunctionalInterfacealgo personalizado SideEffect, 2. a necessidade dessa função auxiliar destaca que algo estranho está acontecendo e provavelmente a abstração está quebrada.
Uthomas # 11/16
530

Use Supplierse não levar nada, mas retornar algo.

Use Consumerse for preciso algo, mas não retorna nada.

Use Callablese retornar um resultado e pode ser lançado (mais parecido com Thunktermos gerais de CS).

Use Runnablese não fizer nada e não puder jogar.

x1a0
fonte
Como exemplo, eu fiz isso chamada de retorno para embrulhar "vazio": public static void wrapCall(Runnable r) { r.run(); }. Obrigado
Maxence
13
bela resposta. Curto e preciso.
Clint Eastwood
Não ajuda se for necessário lançar uma exceção marcada, infelizmente.
Jesse Glick #
13
Como conclusão desta resposta, que não valeria a pena editar: você também pode usar BiConsumer (leva 2, retorna 0), Função (leva 1, retorna 1) e BiFunction (leva 2, retorna 1). Esses são os mais importantes a saber
CLOVIS
2
Existe algo como Callable (que lança uma exceção em seu método call ()), mas requer algum valor de retorno?
Dpelisek 5/05/19
40

O lambda:

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

na verdade representa uma implementação para uma interface como:

public interface Something {
    void action();
}

que é completamente diferente daquele que você definiu. É por isso que você recebe um erro.

Como você não pode estender o seu @FunctionalInterface, nem introduzir um novo, acho que você não tem muitas opções. Você pode usar as Optional<T>interfaces para indicar que alguns dos valores (tipo de retorno ou parâmetro do método) estão ausentes. No entanto, isso não tornará o corpo lambda mais simples.

Konstantin Yovkov
fonte
O problema é que sua Somethingfunção não pode ser um subtipo do meu Actiontipo e não posso ter dois tipos diferentes.
Wickoo
Tecnicamente ele pode, mas ele disse que quer evitá-lo. :)
Konstantin Yovkov
31

Você pode criar uma sub-interface para esse caso especial:

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

Ele usa um método padrão para substituir o método parametrizado herdado Void execute(Void), delegando a chamada ao método mais simples void execute().

O resultado é que é muito mais simples de usar:

Command c = () -> System.out.println("Do nothing!");
Jordão
fonte
De onde vem essa Ação <Vazio, Vazio>? As interfaces Swing e JAX-WX Action não possuem uma interface tão genérica?
luis.espinal 02/07
1
@ luis.espinal: Action<T, U>está declarado na pergunta .....
Jordão
Hahaha, como diabos eu senti falta disso? Obrigado!
luis.espinal 03/07
6

Eu acho que esta tabela é curta e ú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 dito nas outras respostas, a opção apropriada para esse problema é uma Runnable

GabrielRado
fonte
5

Isso não é possível. Uma função que possui um tipo de retorno não nulo (mesmo que seja Void) deve retornar um valor. No entanto, você pode adicionar métodos estáticos ao Actionque permite "criar" 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;
   } 
}

Isso permitiria escrever o seguinte:

// 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));
fabiano
fonte
4

Adicione um método estático dentro da sua interface 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);
    }

}

Resultado

Do nothing!
MCHAppy
fonte
3

Eu não acho que é possível, porque as definições de função não correspondem no seu exemplo.

Sua expressão lambda é avaliada exatamente como

void action() { }

enquanto sua declaração parece

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

como exemplo, se você tiver a seguinte interface

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

o único tipo de função (ao instanciar) que será compatível parece

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

e a declaração ou argumento da falta de retorno fornecerá um erro do compilador.

Portanto, se você declarar uma função que recebe um argumento e retorna um, acho que é impossível convertê-lo em uma função que não menciona acima.

pnadczuk
fonte
3

Apenas para referência, qual interface funcional pode ser usada para referência de método nos casos em que o método lança e / ou retorna um 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
fonte
Por favor, adicione um pouco mais de contexto. Isso parece interessante, mas seu significado não é imediatamente óbvio.
bnieland