web-dev-qa-db-fra.com

Java 8: Lambda avec des arguments variables

Je cherche un moyen d'appeler des méthodes à plusieurs arguments, mais en utilisant une construction lambda. Dans la documentation, il est dit que lambda n'est utilisable que s'il peut mapper vers une interface fonctionnelle.

Je veux faire quelque chose comme:

test((arg0, arg1) -> me.call(arg0, arg1));
test((arg0, arg1, arg2) -> me.call(arg0, arg1, arg2));
...

Existe-t-il un moyen élégant de le faire sans définir 10 interfaces, une pour chaque argument?

Mettre à jour

J'utilise plusieurs interfaces s'étendant d'une interface non-méthode et je surcharge la méthode. 

Exemple pour deux arguments:

interface Invoker {}
interface Invoker2 extends Invoker { void invoke(Object arg0, Object arg1);}
void test(Invoker2 invoker, Object ... arguments) {
    test((Invoker)invoker, Object ... arguments);
}

void test(Invoker invoker, Object ... arguments) {
    //Use Reflection or whatever to access the provided invoker
}

J'espère avoir la possibilité de remplacer les 10 interfaces invocateurs et les 10 méthodes surchargées par une solution unique.

J'ai un cas d'utilisation raisonnable et ne posez pas de questions telles que "Pourquoi voudriez-vous faire une chose pareille?" et 'Quel est le problème que vous essayez de résoudre?' ou quelque chose comme ça. Sachez simplement que j'ai bien réfléchi à cette question et qu'il s'agit d'un problème légitime que je tente de résoudre.

Désolé d’ajouter une confusion en l’appelant invocateur, mais c’est en fait ce qui est appelé dans mon cas d’utilisation actuel (test des contrats de constructeur). 

Comme indiqué ci-dessus, réfléchissez à une méthode qui fonctionne avec un nombre différent d'attributs dans la variable lambda.

13
Martin Kersten

La solution finale que j'utilise actuellement consiste à définir une hiérarchie d'interfaces (comme indiqué dans la question) et à utiliser des méthodes par défaut pour éviter les erreurs. Le pseudo-code ressemble à ceci:

interface VarArgsRunnable {
     default void run(Object ... arguments) {
          throw new UnsupportedOperationException("not possible");
     }
     default int getNumberOfArguments() {
          throw new UnsupportedOperationException("unknown");
     }
}

et une interface pour quatre arguments par exemple:

@FunctionalInterface
interface VarArgsRunnable4 extends VarArgsRunnable {
     @Override
     default void run(Object ... arguments) {
          assert(arguments.length == 4);
          run(arguments[0], arguments[1], arguments[2], arguments[3]);
     }

     void run(Object arg0, Object arg1, Object arg2, Object arg3, Object arg4);

     @Override
     default int getNumberOfArguments() {
          return 4;
     }
}

Après avoir défini 11 interfaces de VarArgsRunnable0 à VarArgsRunnable10, la surcharge d'une méthode devient assez facile.

public void myMethod(VarArgsRunnable runnable, Object ... arguments) {
     runnable.run(arguments);
}

Étant donné que Java ne peut pas composer un fichier Lambda en trouvant l'interface fonctionnelle étendue correcte de VarArgsRunnable en utilisant quelque chose comme instance.myMethod((index, value) -> doSomething(to(index), to(value)), 10, "value"), il est nécessaire de surcharger la méthode à l'aide de l'interface correcte.

public void myMethod(VarArgsRunnable2 runnable, Object arg0, Object arg1) {
    myMethod((VarArgsRunnable)runnable, combine(arg0, arg1));
}

private static Object [] combine(Object ... values) {
    return values;
}

Comme cela nécessite de transtyper Object en n'importe quel type approprié en utilisant to(...), vous pouvez opter pour le paramétrage en utilisant Generics afin d'éviter cette utilisation.

La méthode to- ressemble à ceci: public statique T to (valeur de l'objet) { valeur de retour (T); // supprime cet avertissement }

L'exemple est boiteux mais je l'utilise pour appeler une méthode avec plusieurs arguments constituant une permutation de toutes les combinaisons possibles (à des fins de test) comme:

run((index, value) -> doTheTestSequence(index, value), values(10, 11, 12), values("A", "B", "C"));

Donc, cette petite ligne exécute 6 invocations. Vous voyez donc qu’il s’agit d’une aide précieuse pour pouvoir tester plusieurs éléments sur une seule ligne au lieu d’en définir beaucoup plus ou d’utiliser plusieurs méthodes dans TestNG et ainsi de suite.

PS: Ne pas avoir besoin d’utiliser des réflexions est une bonne chose, puisqu’elle ne peut pas échouer et qu’elle économise beaucoup d’arguments.

2
Martin Kersten

Dans Java, vous devez utiliser un tableau comme celui-ci.

test((Object[] args) -> me.call(args));

Si call prend une variable de tableau args, cela fonctionnera. Sinon, vous pouvez utiliser la réflexion pour passer l'appel.

9
Peter Lawrey

Je crois que le code suivant devrait être adaptable à ce que vous voulez:

public class Main {
    interface Invoker {
      void invoke(Object ... args);
    }

    public static void main(String[] strs) {
        Invoker printer = new Invoker() {
            public void invoke(Object ... args){
                for (Object arg: args) {
                    System.out.println(arg);
                }
            }
        };

        printer.invoke("I", "am", "printing");
        invokeInvoker(printer, "Also", "printing");
        applyWithStillAndPrinting(printer);
        applyWithStillAndPrinting((Object ... args) -> System.out.println("Not done"));
        applyWithStillAndPrinting(printer::invoke);
    }

    public static void invokeInvoker(Invoker invoker, Object ... args) {
        invoker.invoke(args);
    }

    public static void applyWithStillAndPrinting(Invoker invoker) {
        invoker.invoke("Still", "Printing"); 
    }
}

Notez que vous ne devez pas créer et passer un lambda à moi.call car vous avez déjà une référence à cette méthode. Vous pouvez appeler test(me::call) comme je appelle applyWithStillAndPrinting(printer::invoke).

0
user4987274

Ce que j’ai fait, c’était pour mon propre cas d’utilisation: définir une méthode d’aide qui accepte les variables et ensuite invoque le lambda. Mes objectifs étaient de 1) pouvoir définir une fonction dans une méthode de concision et d’étendue (c’est-à-dire le lambda) et 2) faire des appels à ce lambda très succinct. L’affiche originale avait peut-être des objectifs similaires car il avait mentionné, dans l’un des commentaires ci-dessus, vouloir éviter la verbosité de la rédaction d’Object [] {...} pour chaque appel. Cela sera peut-être utile pour les autres.

Étape n ° 1: définir la méthode d'assistance:

public static void accept(Consumer<Object[]> invokeMe, Object... args) {
    invokeMe.accept(args);
}

Étape n ° 2: définissez un lambda pouvant utiliser un nombre variable d'arguments:

Consumer<Object[]> add = args -> {
    int sum = 0;
    for (Object arg : args)
        sum += (int) arg;
    System.out.println(sum);
};

Étape n ° 3: invoquez le lambda plusieurs fois - c'est pour cette raison que je voulais le sucre syntaxique:

accept(add, 1);
accept(add, 1, 2);
accept(add, 1, 2, 3);
accept(add, 1, 2, 3, 4);
accept(add, 1, 2, 3, 4, 5);
accept(add, 1, 2, 3, 4, 5, 6);
0
twm