Java 8:带有可变参数的 Lambda
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32209621/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java 8: Lambda with variable arguments
提问by Martin Kersten
I am looking for a way to invoke multiple argument methods but using a lambda
construct. In the documentation it is said that lambda
is only usable if it can map to a functional interface.
我正在寻找一种调用多个参数方法但使用lambda
构造的方法。在文档中说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
.
采纳答案by Martin Kersten
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 无法通过使用类似的东西找到 VarArgsRunnable 的正确扩展功能接口来组合 Lambda,因此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;
}
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.
由于这需要将 Object 转换为任何合适的类型,因此to(...)
可以使用泛型进行参数化以避免这种用法。
The to
-method looks like this:
public static T to(Object value) {
return (T)value; //Supress this warning
}
本to
-方法看起来是这样的:public static吨至(对象的值){回报(T)值; //取消这个警告 }
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. 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... .
所以这条小线运行了 6 次调用。所以你看到这是一个巧妙的助手,能够在一行中测试多个东西,而不是在 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:不需要使用反射是件好事,因为它不会失败并且非常节省参数计数。
回答by Peter Lawrey
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. If not you can use reflection to make the call instead.
如果call
采用数组变量,args
这将起作用。如果没有,您可以使用反射来代替调用。
回答by twm
What I did was for my own use case was to define a helper methodthat accepts varargs and then invokes the lambda. My goals were to 1) be able to define a function within a method for succinctness and scoping (i.e., the lambda) and 2) make calls to that lambda very succinct. 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. Perhaps this will be useful for others.
我为我自己的用例所做的是定义一个辅助方法,该方法接受可变参数然后调用 lambda。我的目标是 1) 能够在一个方法中定义一个函数以实现简洁和范围(即 lambda)和 2) 非常简洁地调用该 lambda。最初的发布者可能有类似的目标,因为他在上述评论之一中提到,希望避免为每次调用编写 Object[] {...} 的冗长。也许这对其他人有用。
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);
回答by Clement Cherlin
Yes
是的
You do not need helper methods, multiple interfaces, or any other baggage.
您不需要辅助方法、多个接口或任何其他包袱。
However, since Java varargs are implemented using implicit arrays, your lambda will take a single array argument, and have to handle unpacking the argument 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).toString());
}
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> invoker = argArray -> Main.divide(argArray[0], argArray[1]);
test(invoker, 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: java.lang.Boolean cannot be cast to java.lang.Double
at example.Main.lambda$main(Main.java:27)
at example.Main.test(Main.java:13)
at example.Main.main(Main.java:32)
回答by user4987274
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. You can call test(me::call)
just like I call applyWithStillAndPrinting(printer::invoke)
.
请注意,您不必创建 lambda 并将其传递给 me.call,因为您已经拥有对该方法的引用。你可以test(me::call)
像我打电话一样打电话applyWithStillAndPrinting(printer::invoke)
。