简体   繁体   English

Java 8:带有可变参数的 Lambda

[英]Java 8: Lambda with variable arguments

I am looking for a way to invoke multiple argument methods but using a lambda construct.我正在寻找一种方法来调用多个参数方法,但使用lambda构造。 In the documentation it is said that lambda is only usable if it can map to a functional interface.在文档中说lambda只有在它可以映射到功能接口时才可用。

I want to do something like:我想做类似的事情:

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

Is there any way one can do this elegantly without defining 10 interfaces, one for each argument count?有没有什么办法可以在不定义 10 个接口的情况下优雅地做到这一点,每个参数计数一个?

Update更新

I use multiple interfaces extending from a non-method interface and I overload the method.我使用从非方法接口扩展的多个接口,并重载了该方法。

Example for two 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
}

I hope for a possibility to replace the 10 invoker interfaces and the 10 overloaded methods with a single solution.我希望有可能用一个解决方案替换 10 个调用者接口和 10 个重载方法。

I have a reasonable use case and please do not ask questions like 'Why would you do such a thing?'我有一个合理的用例,请不要问诸如“你为什么要这样做?”之类的问题。 and 'What is the problem you are trying to solve?'以及“您要解决的问题是什么?” or anything like that.或类似的东西。 Just know that I have thought this through and this is a legitimate problem I'm try to solve.只要知道我已经考虑过这一点,这是我试图解决的合法问题。

Sorry to add confusion calling it invoker but it is actually what it is called in my current use case (testing constructor contracts).很抱歉添加混淆调用它,但它实际上是我当前用例(测试构造函数合同)中调用的内容。

Basically, as stated above, think about a method that works with a different number of attributes within the lambda .基本上,如上所述,请考虑使用lambda不同数量的属性的方法。

In Java you need to use an array like this.Java您需要使用这样的数组。

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

If call takes an array variable args this will work.如果call采用数组变量args这将起作用。 If not you can use reflection to make the call instead.如果没有,您可以使用反射来代替调用。

The final solution I currently use is defining a hierarchy of interfaces (as stated in the question) and use default methods to avoid failure.我目前使用的最终解决方案是定义接口层次结构(如问题中所述)并使用默认方法来避免失败。 Pseudo code looks like this:伪代码如下所示:

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

and a interface for four arguments for instance:和四个参数的接口,例如:

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

Having defined 11 interfaces from VarArgsRunnable0 to VarArgsRunnable10 overloading a method becomes quite easy.定义了从 VarArgsRunnable0 到 VarArgsRunnable10 的 11 个接口,重载一个方法变得非常容易。

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

Since Java can not compose a Lambda by finding the correct extended functional interface of VarArgsRunnable by using something like instance.myMethod((index, value) -> doSomething(to(index), to(value)), 10, "value") one need to overload the method using the correct interface.由于 Java 无法通过使用诸如instance.myMethod((index, value) -> doSomething(to(index), to(value)), 10, "value")东西找到 VarArgsRunnable 的正确扩展功能接口来组合 Lambda需要使用正确的接口重载该方法。

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

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

Since this requires to cast Object to any appropriated type using to(...) one can go for parameterization using Generics in order to avoid this usage.由于这需要使用to(...)将 Object 转换为任何合适的类型to(...)可以使用泛型进行参数化以避免这种用法。

The to -method looks like this: public static T to(Object value) { return (T)value; to方法看起来像这样: public static T to(Object value) { return (T)value; } //Supress this warning } //取消这个警告 }

The example is lame but I use it to call a method with multiple arguments being a permutation of all potential combinations (for testing purposes) like:这个例子很蹩脚,但我用它来调用一个带有多个参数的方法,这些参数是所有潜在组合的排列(用于测试目的),例如:

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

So this little line runs 6 invocations.所以这条小线运行了 6 次调用。 So you see this is a neat helper being able to test multiple stuff in a single line instead of defining a lot more or use multiple methods in TestNG and whatever... .所以你看到这是一个巧妙的助手,能够在一行中测试多个东西,而不是在 TestNG 中定义更多或使用多个方法等等......。

PS: Having no need to use reflections is quite a good thing, since it can not fail and is quite save argument count wise. PS:不需要使用反射是件好事,因为它不会失败并且非常节省参数计数。

What I did was for my own use case was to define a helper method that accepts varargs and then invokes the lambda.我为我自己的用例所做的是定义一个辅助方法,该方法接受可变参数然后调用 lambda。 My goals were to 1) be able to define a function within a method for succinctness and scoping (ie, the lambda) and 2) make calls to that lambda very succinct.我的目标是 1) 能够在一个方法中定义一个函数以实现简洁和范围(即 lambda)和 2) 非常简洁地调用该 lambda。 The original poster may have had similar goals since he mentioned, in one of the comments above, wanting to avoid the verbosity of writing Object[] {...} for every call.最初的发布者可能有类似的目标,因为他在上面的评论之一中提到,希望避免为每次调用编写 Object[] {...} 的冗长。 Perhaps this will be useful for others.也许这对其他人有用。

Step #1: define the helper method:第 1 步:定义辅助方法:

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

Step #2: define a lambda which can use a varying number of arguments:第 2 步:定义一个可以使用不同数量参数的 lambda:

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

Step #3: invoke the lambda many times - this succinctness was why I wanted the syntactic sugar:第 3 步:多次调用 lambda - 这种简洁性就是我想要语法糖的原因:

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

I was curious if a lambda for a variable number of arguments would work.我很好奇用于可变数量参数的 lambda 是否可行。 Indeed it seems to.确实如此。 See:看:

public class Example {
    public interface Action<T> {
        public void accept(T... ts);
    }

    public static void main(String args[]) {
        // Action<String> a = (String... x) -> { also works
        Action<String> a = (x) -> {
            for(String s : x) {
                System.out.println(s);
            }
        };

        a.accept("Hello", "World");
    }
}

I know the question has been answered.我知道问题已经回答了。 This is mainly for others who are curious and come across this post.这主要是为其他好奇并遇到这篇文章的人准备的。

I believe the following code should be adaptable to what you want:我相信以下代码应该适合您的需求:

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

Note that you don't have to create and pass in a lambda to me.call because you already have a reference to that method.请注意,您不必创建 lambda 并将其传递给 me.call,因为您已经拥有对该方法的引用。 You can call test(me::call) just like I call applyWithStillAndPrinting(printer::invoke) .您可以像调用applyWithStillAndPrinting(printer::invoke)一样调用test(me::call) applyWithStillAndPrinting(printer::invoke)

Yes是的

You do not need overloaded methods or multiple interfaces.您不需要重载方法或多个接口。 It can be done using a single functional interface with a variable argument list method.它可以使用带有可变参数列表方法的单个功能接口来完成。

However, since Java varargs are implemented using implicit arrays, your lambda will take a single generic array argument , and have to handle unpacking the arguments from the array.但是,由于 Java 可变参数是使用隐式数组实现的,因此您的 lambda 将采用单个通用数组参数,并且必须处理从数组中解包参数。

You will also have to handle type conversions if your function has arguments that are not all of the same class, with all the inherent danger that entails.如果您的函数的参数不完全属于同一个类,那么您还必须处理类型转换,这会带来所有固有的危险。

Example例子

package example;
import java.util.Arrays;
import java.util.List;

public class Main {
    @FunctionalInterface
    public interface Invoker<T, R> {
        R invoke(T... args);
    }

    @SafeVarargs
    public static <T, R> void test(Invoker<T, R> invoker, T...args) {
        System.out.println("Test result: " + invoker.invoke(args));
    }

    public static Double divide(Integer a, Integer b) {
        return a / (double)b;
    }

    public static String heterogeneousFunc(Double a, Boolean b, List<String> c) {
        return a.toString() + ", " + b.hashCode() + ", " + c.size();
    }

    public static void main(String[] args) {
        Invoker<Integer, Double> divideInvoker = argArray -> Main.divide(
                argArray[0], argArray[1]
        );

        test(divideInvoker, 22, 7);

        Invoker<Object, String> weirdInvoker = argArray -> heterogeneousFunc(
                (Double) argArray[0], (Boolean) argArray[1], (List<String>) argArray[2]
        );

        test(weirdInvoker, 1.23456d, Boolean.TRUE, Arrays.asList("a", "b", "c", "d"));

        test(weirdInvoker, Boolean.FALSE, Arrays.asList(1, 2, 3), 9.999999d);
    }
}


Output:输出:

Test result: 3.142857142857143
Test result: 1.23456, 1231, 4
Exception in thread "main" java.lang.ClassCastException: class java.lang.Boolean cannot be cast to class java.lang.Double
    at example.Main.lambda$main$1(Main.java:27)
    at example.Main.test(Main.java:13)
    at example.Main.main(Main.java:32)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM