Argument Java 8 lambda Void

188

Załóżmy, że mam następujący interfejs funkcjonalny w Javie 8:

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

A w niektórych przypadkach potrzebuję akcji bez argumentów lub typu zwracanego. Piszę więc coś takiego:

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

Daje mi to jednak błąd kompilacji, muszę go zapisać jako

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

Co jest brzydkie. Czy jest jakiś sposób na pozbycie się Voidparametru type?

Wickoo
źródło
1
Spójrz na stackoverflow.com/questions/14319787/…
BobTheBuilder
7
Jeśli potrzebujesz akcji, tak jak ją zdefiniowałeś, nie jest to możliwe. Jednak twój pierwszy przykład może pasować do Runnabletego, czego szukaszRunnable r = () -> System.out.println("Do nothing!");
Alexis C.
1
@BobTheBuilder Nie chcę używać konsumenta, jak sugerowano w tym poście.
Wickoo
2
Odpowiedź Matta sprawia, że ​​typy działają, ale co robi osoba dzwoniąca, gdy otrzyma zerową wartość zwracaną?
Stuart Marks
8
Możesz trzymać kciuki i mieć nadzieję, że sugestie 2 i 3 w tym poście zostaną zaakceptowane dla Java 9!
assylias

Odpowiedzi:

110

Składnia, której szukasz, jest możliwa dzięki niewielkiej funkcji pomocniczej, która konwertuje a Runnablena Action<Void, Void>(możesz to umieścić Actionna przykład):

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
źródło
4
Jest to najczystsze obejście, jakie można uzyskać, IMO, więc +1 (lub za pomocą metody statycznej w samym interfejsie)
Alexis C.,
Poniższe rozwiązanie Konstantina Yovkova (z @FunctionalInterface) jest lepszym rozwiązaniem, ponieważ nie wymaga on generycznych i nie wymaga dodatkowego kodu.
uthomas
@uthomas Niestety, nie widzę odpowiedzi dotyczącej @FunctionalInterface. Mówi jedynie, że nie można go przedłużyć ...
Matt
1
Cześć @Matt, przepraszam. Za szybko zareagowałem. Na podane pytanie odpowiedź jest całkowicie poprawna. Niestety mój głos jest zablokowany, więc nie mogę usunąć mojego -1 w tej odpowiedzi. Dwie uwagi: 1. Zamiast Runnabledziałania należy podjąć niestandardowe @FunctionalInterfacecoś o nazwie SideEffect, 2. potrzeba takiej funkcji pomocnika podkreśla, że ​​dzieje się coś dziwnego i prawdopodobnie abstrakcja jest zepsuta.
uthomas
530

Użyj, Supplierjeśli nic nie bierze, ale coś zwraca.

Użyj, Consumerjeśli coś zabiera, ale nic nie zwraca.

Użyj, Callablejeśli zwróci wynik i może rzucić (najbardziej zbliżony do Thunkogólnych warunków CS).

Użyj, Runnablejeśli nie robi ani nie może rzucać.

x1a0
źródło
Jako przykład zrobiłem to zawinąć „void” połączenia zwrotnego: public static void wrapCall(Runnable r) { r.run(); }. Dzięki
Maxence
13
piękna odpowiedź. Krótki i precyzyjny.
Clint Eastwood
Niestety nie pomaga, jeśli musi zgłosić sprawdzony wyjątek.
Jesse Glick,
13
Jako uzupełnienie tej odpowiedzi, która nie byłaby warta edycji: możesz także użyć BiConsumer (zajmuje 2, zwraca 0), Function (bierze 1, zwraca 1) i BiFunction (bierze 2, zwraca 1). Są to najważniejsze, aby wiedzieć
CLOVIS
2
Czy istnieje coś takiego jak Callable (który zgłasza wyjątek w metodzie call ()), ale wymaga wartości zwracanej?
dpelisek
40

The lambda:

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

faktycznie reprezentuje implementację interfejsu takiego jak:

public interface Something {
    void action();
}

który jest zupełnie inny niż ten, który zdefiniowałeś. Dlatego pojawia się błąd.

Ponieważ nie możesz przedłużyć @FunctionalInterfaceani wprowadzić nowego, myślę, że nie masz wielu opcji. Optional<T>Interfejsów można jednak użyć do wskazania, że ​​brakuje niektórych wartości (typu zwracanego parametru lub parametru metody). Nie uprości to jednak ciała lambda.

Konstantin Yovkov
źródło
Problem polega na tym, że twoja Somethingfunkcja nie może być podtypem mojego Actiontypu i nie mogę mieć dwóch różnych typów.
Wickoo,
Z technicznego punktu widzenia może, ale powiedział, że chce tego uniknąć. :)
Konstantin Yovkov
31

Możesz utworzyć pod-interfejs dla tego specjalnego przypadku:

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

Używa domyślnej metody, aby zastąpić odziedziczoną sparametryzowaną metodę Void execute(Void), delegując wywołanie do prostszej metody void execute().

W rezultacie korzystanie z niego jest znacznie prostsze:

Command c = () -> System.out.println("Do nothing!");
Jordão
źródło
Skąd pochodzi ta akcja <Pustka, Pustka>? Ani interfejsy Swing, ani JAX-WX Action nie mają tak ogólnego interfejsu?
luis.espinal
1
@ luis.espinal: Action<T, U>zadeklarowano w pytaniu .....
Jordão
Hahaha, jak do diabła tęskniłem? Dzięki!
luis.espinal
6

Myślę, że ta tabela jest krótka i przydatna:

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

Jak powiedziano na inne odpowiedzi, odpowiednią opcją dla tego problemu jest Runnable

GabrielRado
źródło
5

Nie jest możliwe. Funkcja, która ma typ zwrotny nieważny (nawet jeśli jest Void), musi zwrócić wartość. Można jednak dodać do Actiontego metody statyczne, które pozwalają „utworzyć” 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;
   } 
}

To pozwoli ci napisać:

// 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));
fabiański
źródło
4

Dodaj metodę statyczną do interfejsu funkcjonalnego

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

}

Wynik

Do nothing!
MCHAppy
źródło
3

Nie sądzę, aby było to możliwe, ponieważ definicje funkcji nie pasują do twojego przykładu.

Twoje wyrażenie lambda jest oceniane dokładnie tak jak

void action() { }

podczas gdy twoja deklaracja wygląda

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

jako przykład, jeśli masz następujący interfejs

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

jedyny rodzaj funkcji (podczas tworzenia instancji), który będzie kompatybilny

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

a brak instrukcji return lub argumentu spowoduje błąd kompilatora.

Dlatego jeśli zadeklarujesz funkcję, która pobiera argument i zwraca jedną, myślę, że nie jest możliwe przekonwertowanie go na funkcję, która nie jest wymieniona powyżej.

pnadczuk
źródło
3

Tylko dla odniesienia, który interfejs funkcjonalny może być użyty do odwołania do metody w przypadkach, gdy metoda rzuca i / lub zwraca wartość.

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
źródło
Dodaj więcej kontekstu. Wygląda to interesująco, ale jego znaczenie nie jest od razu oczywiste.
bnieland