Argument Java 8 lambda Void


188

Disons que j'ai l'interface fonctionnelle suivante en Java 8:

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

Et dans certains cas, j'ai besoin d'une action sans arguments ni type de retour. Alors j'écris quelque chose comme ça:

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

Cependant, cela me donne une erreur de compilation, je dois l'écrire comme

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

Ce qui est moche. Existe-t-il un moyen de se débarrasser du Voidparamètre de type?



7
Si vous avez besoin d'une action, telle que vous l'avez définie, ce n'est pas possible. Cependant, votre premier exemple pourrait tenir dans un Runnable, que vous recherchezRunnable r = () -> System.out.println("Do nothing!");
Alexis C.

1
@BobTheBuilder Je ne veux pas utiliser un consommateur comme suggéré dans cet article.
Wickoo

2
La réponse de Matt fait fonctionner les types, mais que fait l'appelant quand il obtient une valeur de retour nulle?
Stuart marque le

8
Vous pourriez croiser les doigts et espérer que les suggestions 2 et 3 de cet article seront acceptées pour Java 9!
assylias

Réponses:


110

La syntaxe que vous recherchez est possible avec une petite fonction d'aide qui convertit un Runnableen Action<Void, Void>(vous pouvez le placer Actionpar exemple):

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
C'est la solution de contournement la plus propre que vous puissiez obtenir, IMO, donc +1 (ou avec une méthode statique dans l'interface elle-même)
Alexis C.

La solution de Konstantin Yovkov ci-dessous (avec @FunctionalInterface) est une meilleure solution, car elle n'implique pas de génériques et ne nécessite pas de code supplémentaire.
uthomas

@uthomas Désolé, je ne vois pas de réponse impliquant @FunctionalInterface. Il dit simplement qu'il n'est pas possible de l'étendre ...
Matt

1
Salut @Matt, désolé. J'ai réagi trop vite. Pour la question donnée à laquelle vous répondez est parfaitement valable. Malheureusement, mon vote est verrouillé, je ne peux donc pas supprimer mon -1 sur cette réponse. Deux remarques: 1. Au lieu de l' Runnableaction devrait prendre un @FunctionalInterfacequelque chose de personnalisé appelé SideEffect, 2. le besoin d'une telle fonction d'aide met en évidence que quelque chose de bizarre se passe et probablement l'abstraction est brisée.
uthomas

531

À utiliser Suppliersi cela ne prend rien, mais renvoie quelque chose.

À utiliser Consumersi cela prend quelque chose, mais ne renvoie rien.

À utiliser Callablesi elle renvoie un résultat et peut lancer (la plupart s'apparente Thunken termes généraux de CS).

À utiliser Runnables'il ne fait ni l'un ni l'autre et ne peut pas lancer.


À titre d'exemple , je l'ai fait pour envelopper appel de retour « vide »: public static void wrapCall(Runnable r) { r.run(); }. Merci
Maxence

13
belle réponse. Court et précis.
Clint Eastwood

Cela n'aide pas s'il doit lever une exception vérifiée, malheureusement.
Jesse Glick

13
Pour compléter cette réponse, qui ne vaudrait pas la peine d'être modifiée: vous pouvez également utiliser BiConsumer (prend 2, retourne 0), Function (prend 1, retourne 1) et BiFunction (prend 2, retourne 1). Ce sont les plus importants à savoir
CLOVIS

2
Existe-t-il quelque chose comme Callable (qui lève une exception dans sa méthode call ()) mais nécessite une valeur de retour?
dpelisek

40

Le lambda:

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

représente en fait une implémentation pour une interface comme:

public interface Something {
    void action();
}

qui est complètement différent de celui que vous avez défini. C'est pourquoi vous obtenez une erreur.

Puisque vous ne pouvez pas étendre votre @FunctionalInterface, ni en introduire un nouveau, je pense que vous n'avez pas beaucoup d'options. Optional<T>Cependant, vous pouvez utiliser les interfaces pour indiquer que certaines des valeurs (type de retour ou paramètre de méthode) sont manquantes. Cependant, cela ne rendra pas le corps lambda plus simple.


Le problème est que votre Somethingfonction ne peut pas être un sous-type de mon Actiontype et que je ne peux pas avoir deux types différents.
Wickoo

Techniquement, il le peut, mais il a dit qu'il voulait l'éviter. :)
Konstantin Yovkov

31

Vous pouvez créer une sous-interface pour ce cas particulier:

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

Il utilise une méthode par défaut pour remplacer la méthode paramétrée héritée Void execute(Void), déléguant l'appel à la méthode la plus simple void execute().

Le résultat est qu'il est beaucoup plus simple à utiliser:

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

D'où vient cette action <Void, Void>? Ni les interfaces Swing ni les interfaces JAX-WX Action n'ont une interface aussi générique?
luis.espinal le

1
@ luis.espinal: Action<T, U>est déclaré dans la question .....
Jordão le

Hahaha, comment diable ai-je raté ça? Merci!
luis.espinal le

6

Je pense que ce tableau est court et 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

Comme indiqué dans les autres réponses, l'option appropriée pour ce problème est un Runnable


5

Ce n'est pas possible. Une fonction qui a un type de retour non void (même si c'est Void) doit renvoyer une valeur. Cependant, vous pouvez ajouter des méthodes statiques Actionqui vous permettent de "créer" 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;
   } 
}

Cela vous permettrait d'écrire ce qui suit:

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

Ajoutez une méthode statique dans votre interface fonctionnelle

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

}

Production

Do nothing!

3

Je ne pense pas que ce soit possible, car les définitions de fonction ne correspondent pas à votre exemple.

Votre expression lambda est évaluée exactement comme

void action() { }

alors que votre déclaration ressemble à

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

à titre d'exemple, si vous avez l'interface suivante

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

le seul type de fonction (lors de l'instanciation) qui sera compatible ressemble à

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

et l'absence de déclaration ou d'argument de retour vous donnera une erreur de compilation.

Par conséquent, si vous déclarez une fonction qui prend un argument et en renvoie un, je pense qu'il est impossible de la convertir en fonction qui ne fait ni l'un ni l'autre des mentionnés ci-dessus.


3

Juste pour référence, quelle interface fonctionnelle peut être utilisée pour la référence de méthode dans les cas où la méthode lève et / ou renvoie une valeur.

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
}

Veuillez ajouter un peu plus de contexte. Cela semble intéressant, mais sa signification n'est pas immédiatement évidente.
bnieland
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.