it-roy-ru.com

Java 8: лямбда с переменными аргументами

Я ищу способ для вызова нескольких аргументов методов, но с использованием конструкции lambda. В документации сказано, что lambda можно использовать, только если он может отображаться на функциональный интерфейс.

Я хочу сделать что-то вроде:

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

Есть ли способ сделать это элегантно без определения 10 интерфейсов, по одному для каждого аргумента?

Обновление

Я использую несколько интерфейсов, выходящих из не-метода интерфейса, и я перегружаю метод. 

Пример для двух аргументов:

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
}

Я надеюсь на возможность заменить 10 интерфейсов invoker и 10 перегруженных методов одним решением.

У меня есть разумный вариант использования, и, пожалуйста, не задавайте вопросы типа «Зачем вы это делаете?» и «Какую проблему вы пытаетесь решить?» или что-нибудь в этом роде. Просто знайте, что я все продумал, и это законная проблема, которую я пытаюсь решить.

Извините, что добавил путаницу, называя это invoker, но на самом деле это так и называется в моем текущем сценарии использования (тестирование контрактов с конструкторами). 

В основном, как указано выше, подумайте о методе, который работает с другим количеством атрибутов внутри lambda.

13
Martin Kersten

Последнее решение, которое я сейчас использую, - это определение иерархии интерфейсов (как указано в вопросе) и использование методов по умолчанию, чтобы избежать сбоев. Псевдокод выглядит так:

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

и интерфейс для четырех аргументов, например:

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

Определив 11 интерфейсов от VarArgsRunnable0 до VarArgsRunnable10, перегрузка метода становится довольно простой.

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

Поскольку Java не может составить лямбду, найдя правильный расширенный функциональный интерфейс VarArgsRunnable, используя что-то вроде instance.myMethod((index, value) -> doSomething(to(index), to(value)), 10, "value"), необходимо перегрузить метод, используя правильный интерфейс.

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

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

Поскольку для этого требуется привести Object к любому присвоенному типу, используя to(...), можно использовать параметризацию с использованием Generics, чтобы избежать такого использования.

Метод to- выглядит следующим образом: public static T to (значение объекта) { возвращаемое (T) значение; // Подавить это предупреждение }

Пример является неудачным, но я использую его для вызова метода с несколькими аргументами, представляющими собой перестановку всех потенциальных комбинаций (для целей тестирования), например:

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

Итак, эта маленькая строка запускает 6 вызовов. Итак, вы видите, что это аккуратный помощник, способный тестировать несколько вещей в одной строке вместо того, чтобы определять намного больше или использовать несколько методов в TestNG и так далее ....

PS: Отсутствие необходимости использовать отражения - это очень хорошо, так как оно не может потерпеть неудачу и является вполне разумным аргументом.

2
Martin Kersten

В Java вам нужно использовать такой массив.

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

Если call принимает переменную массива args, это будет работать. Если нет, вы можете использовать отражение, чтобы сделать звонок.

9
Peter Lawrey

Я считаю, что следующий код должен быть адаптирован к тому, что вы хотите:

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

Обратите внимание, что вам не нужно создавать и передавать лямбду me.call, потому что у вас уже есть ссылка на этот метод. Вы можете позвонить test(me::call) так же, как я звоню applyWithStillAndPrinting(printer::invoke).

0
user4987274

То, что я сделал, было для моего собственного случая использования определить вспомогательный метод , который принимает varargs, а затем вызывает лямбду. Моими целями было: 1) уметь определить функцию в методе краткости и определения объема (т.е. лямбда) и 2) сделать вызовы к этой лямбде очень краткими. Исходный плакат, возможно, имел аналогичные цели, так как он упомянул в одном из комментариев выше, желая избежать многословия написания Object [] {...} для каждого вызова. Возможно, это будет полезно для других.

Шаг № 1: определить вспомогательный метод:

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

Шаг № 2: определить лямбду, которая может использовать различное количество аргументов:

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

Шаг № 3: многократно вызывать лямбду - именно из-за этой краткости я хотел получить синтаксический сахар:

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