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?



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

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ć.


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.


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

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


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

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!

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.


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
}

Dodaj więcej kontekstu. Wygląda to interesująco, ale jego znaczenie nie jest od razu oczywiste.
bnieland
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.