webentwicklung-frage-antwort-db.com.de

Java 8: Lambda mit variablen Argumenten

Ich suche nach einer Möglichkeit, mehrere Argumentmethoden aufzurufen, aber ein lambda-Konstrukt zu verwenden. In der Dokumentation heißt es, dass lambda nur verwendbar ist, wenn es einer funktionalen Schnittstelle zugeordnet werden kann.

Ich möchte etwas tun wie:

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

Gibt es eine Möglichkeit, dies elegant zu tun, ohne 10 Schnittstellen zu definieren, eine für jedes Argument zählt?

Update

Ich verwende mehrere Schnittstellen, die von einer Nicht-Methoden-Schnittstelle ausgehen, und überlaste die Methode. 

Beispiel für zwei Argumente:

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
}

Ich hoffe auf eine Möglichkeit, die 10 Aufruferschnittstellen und die 10 überladenen Methoden durch eine einzige Lösung zu ersetzen.

Ich habe einen vernünftigen Anwendungsfall und bitte stellen Sie keine Fragen wie "Warum würden Sie so etwas tun?" und "Was ist das Problem, das Sie zu lösen versuchen?" oder so etwas. Ich muss nur wissen, dass ich das durchdacht habe und dies ist ein legitimes Problem, das ich zu lösen versuche.

Es tut mir leid, wenn Sie Verwirrung hinzufügen, wenn Sie es als Aufrufer aufrufen, aber es ist tatsächlich so, wie es in meinem aktuellen Anwendungsfall (Testen von Konstruktorverträgen) heißt. 

Denken Sie, wie bereits gesagt, über eine Methode nach, die mit einer anderen Anzahl von Attributen innerhalb von lambda arbeitet.

13
Martin Kersten

Die letzte Lösung, die ich derzeit verwende, ist das Definieren einer Hierarchie von Schnittstellen (wie in der Frage angegeben). Verwenden Sie Standardmethoden, um Fehler zu vermeiden. Der Pseudocode sieht folgendermaßen aus:

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

und eine Schnittstelle für vier Argumente zum Beispiel:

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

Nachdem Sie 11 Schnittstellen von VarArgsRunnable0 bis VarArgsRunnable10 definiert haben, wird das Überladen einer Methode zum Kinderspiel.

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

Da Java kein Lambda erstellen kann, indem die richtige erweiterte Funktionsschnittstelle von VarArgsRunnable mit etwas wie instance.myMethod((index, value) -> doSomething(to(index), to(value)), 10, "value") gefunden wird, muss die Methode mit der richtigen Schnittstelle überladen werden.

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

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

Da dies erforderlich ist, um Object mit to(...) in einen geeigneten Typ umzuwandeln, können Sie mit Generics eine Parametrisierung vornehmen, um diese Verwendung zu vermeiden.

Die to- Methode sieht folgendermaßen aus: public static T to (Objektwert) { Rückgabewert (T); // Diese Warnung unterdrücken }

Das Beispiel ist lahm, aber ich benutze es, um eine Methode aufzurufen, bei der mehrere Argumente eine Permutation aller möglichen Kombinationen sind (zu Testzwecken):

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

Diese kleine Zeile führt also 6 Aufrufe aus. Sie sehen also, dass dies ein netter Helfer ist, der in der Lage ist, mehrere Elemente in einer einzelnen Zeile zu testen, anstatt viel mehr zu definieren oder mehrere Methoden in TestNG und was auch immer zu verwenden.

PS: Es ist eine gute Sache, keine Reflexionen verwenden zu müssen, da sie nicht versagen kann und Argumente sicher zählbar sind.

2
Martin Kersten

In Java müssen Sie ein solches Array verwenden.

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

Wenn call eine Array-Variable args verwendet, funktioniert dies. Ist dies nicht der Fall, können Sie stattdessen den Anruf über Spiegeln tätigen.

9
Peter Lawrey

Ich glaube, der folgende Code sollte an Ihre Wünsche angepasst werden können:

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

Beachten Sie, dass Sie kein Lambda erstellen und an me.call übergeben müssen, da Sie bereits einen Verweis auf diese Methode haben. Sie können test(me::call) genauso wie ich applyWithStillAndPrinting(printer::invoke) aufrufen.

0
user4987274

Was ich tat, war für meinen eigenen Anwendungsfall, definiere eine Hilfsmethode, _, die Varargs akzeptiert und dann das Lambda aufruft. Meine Ziele waren, 1) in der Lage zu sein, eine Funktion innerhalb einer Methode für Prägnanz und Scoping (d. H. Das Lambda) zu definieren, und 2) Aufrufe dieses Lambda sehr knapp zu machen. Das ursprüngliche Poster hatte möglicherweise ähnliche Ziele, da er in einem der obigen Kommentare erwähnt hatte, dass er die Ausführlichkeit des Schreibens von Object [] {...} für jeden Aufruf vermeiden wollte. Vielleicht wird das für andere nützlich sein.

Schritt 1: Definiere die Hilfsmethode:

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

Schritt 2: Definieren Sie ein Lambda, das eine unterschiedliche Anzahl von Argumenten verwenden kann:

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

Schritt # 3: Rufen Sie das Lambda viele Male auf - diese Prägnanz war der Grund, warum ich den syntaktischen Zucker wollte:

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