Java 8:带有可变参数的 Lambda
Posted
技术标签:
【中文标题】Java 8:带有可变参数的 Lambda【英文标题】:Java 8: Lambda with variable arguments 【发布时间】:2015-11-19 11:44:35 【问题描述】:我正在寻找一种调用多个参数方法但使用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 个调用程序接口和 10 个重载方法。
我有一个合理的用例,请不要问诸如“你为什么要做这样的事情?”之类的问题。和“你要解决的问题是什么?”或类似的东西。只要知道我已经考虑过了,这是我正在尝试解决的合理问题。
很抱歉添加混淆调用它,但它实际上是在我当前的用例(测试构造函数合同)中调用的。
基本上,如上所述,请考虑一种方法,该方法适用于lambda
中不同数量的属性。
【问题讨论】:
简短回答:不。长答案:nooooooooo。 (但是,无论如何,你会怎么处理这样的事情呢?) 你所有的论点都是同一类型吗?如果是这样,您是否考虑过使用可变参数?有什么具体的例子可以说明你正在尝试做什么? Louis 对不起,我为你更新了(update2)。 您使用可变数量的参数但不能使用可变参数是否有任何特殊原因? 公平地说,yak shaving 是一回事,我想问“你真正想做什么?”总是一个公平的问题...... 【参考方案1】:在Java
中你需要使用这样的数组。
test((Object[] args) -> me.call(args));
如果call
采用数组变量args
,这将起作用。如果没有,您可以使用反射来进行调用。
【讨论】:
我添加了一个更新来告诉你我目前的状态。 Object [] args 正是我遇到的墙,我在周围工作。现在我想尽可能减少 Java 8 允许的解决方法。 @MartinKersten Java 有一个静态编译器,这意味着它只能针对编译时已知的方法调用进行编译。如果直到运行时才知道类型是什么,则必须使用反射。 我想使用 lambdas 变量参数。我确切地知道反射是什么,但是在您的示例中,您必须为每个冗长的调用提供一个新的 Object [] arg0, arg1 ,我想避免这种情况。所以用 arg0, arg1 给出你的解决方案是我正在寻找的真正交易。反射对此无济于事,但它是解决方案的一部分:-)。 @MartinKersten 处理可变长度参数的方法是使用数组,因为它可以是不同的长度。 这会破坏使用 lambdas 来减少冗长和样板的目的。目前我可以做类似 testNullPointer((a, b) -> new TestClass(to(a), to(b)), "a", null);以及 testNullPointer((a, b, c, d) -> new TestClass(to(a), to(b), to(c), to(d)), "a", null, 3, new Date ());这正是我想用更少的接口(目前使用重载 10 次的方法和 11 个不同的接口来支持零到 10 个参数)但使用默认方法我不再需要反射(这非常好)。跨度> 【参考方案2】:我目前使用的最终解决方案是定义接口的层次结构(如问题中所述)并使用默认方法来避免失败。伪代码如下:
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;
定义了从 VarArgsRunnable0 到 VarArgsRunnable10 的 11 个接口,重载一个方法变得非常容易。
public void myMethod(VarArgsRunnable runnable, Object ... arguments)
runnable.run(arguments);
由于 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;
由于这需要使用 to(...)
将 Object 转换为任何适当的类型,因此可以使用泛型进行参数化以避免这种用法。
to
-方法如下所示:
公共静态 T 到(对象值)
返回(T)值; //禁止这个警告
这个例子很蹩脚,但我用它来调用一个具有多个参数的方法,这些参数是所有潜在组合的排列(用于测试目的),例如:
run((index, value) -> doTheTestSequence(index, value), values(10, 11, 12), values("A", "B", "C"));
所以这条小线运行了 6 次调用。所以你看到这是一个简洁的助手,能够在一行中测试多个东西,而不是定义更多或在 TestNG 中使用多种方法等等......。
PS:不需要使用反射是一件好事,因为它不会失败并且非常节省参数计数。
【讨论】:
这是一个实用的解决方案,但同时它也说明了 Java 只能被扭曲来做这种事情。我想知道是否有一种优雅的方式。【参考方案3】:我为自己的用例所做的是定义一个辅助方法,它接受可变参数,然后调用 lambda。我的目标是 1) 能够在方法中定义一个函数以保持简洁和范围(即 lambda)和 2) 对该 lambda 的调用非常简洁。最初的发布者可能有类似的目标,因为他在上面的一个 cmets 中提到,希望避免为每个调用编写 Object[] ... 的冗长。也许这对其他人有用。
步骤#1:定义辅助方法:
public static void accept(Consumer<Object[]> invokeMe, Object... args)
invokeMe.accept(args);
第 2 步:定义一个可以使用不同数量参数的 lambda:
Consumer<Object[]> add = args ->
int sum = 0;
for (Object arg : args)
sum += (int) arg;
System.out.println(sum);
;
第 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);
【讨论】:
【参考方案4】:我很好奇用于可变数量参数的 lambda 是否可以工作。确实如此。见:
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");
我知道问题已得到解答。这主要是为那些好奇并看到这篇文章的人准备的。
【讨论】:
【参考方案5】:我相信下面的代码应该可以适应你想要的:
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");
请注意,您不必创建 lambda 并将其传递给 me.call,因为您已经拥有对该方法的引用。你可以打电话给test(me::call)
,就像我打电话给applyWithStillAndPrinting(printer::invoke)
一样。
【讨论】:
感谢您的代码,但我正在寻找使用 lambdas。调用程序示例就是我所说的,因为它是关于调用测试方法生成测试场景的。 请注意,lambda 只是一个调用站点构造,请参阅***.com/questions/13604703/…。 上面的调用者是 一个 lambda。打印机只是为了方便而存在,除非您遇到的问题是如何定义 lambda。 调用者不是 lambda。 lambda 是 (arg0, arg1) -> doSomething(); 形式的东西。一个 lambda 被转换(映射)为一个函数式接口——通常被认为是用 @FunctionalInterface 注释的,但它变成了可选的,所以如果可能的话,任何接口都可以使用。因此,尝试将您的调用程序作为方法的参数并使用 (arg0, arg1) -> System.println("message " + arg0 + ", " +arg1);。对 (arg0, arg1, arg2) 执行相同操作 -> System.println("message " + arg0 + ", " + arg1 + ", " + arg2)。 上面的printer
声明完全等同于Invoker printLambda = (Object ... args) -> for (Object arg: args) System.out.println(arg); ;
。用 ->
定义的 Lambda 只是语法糖;两者是等价的。【参考方案6】:
是的
您不需要重载方法或多个接口。可以使用带有可变参数列表方法的单个功能接口来完成。
但是,由于 Java 可变参数是使用隐式数组实现的,因此您的 lambda 将采用单个通用数组参数,并且必须处理从数组中解包参数。
如果您的函数的参数并非全部属于同一个类,那么您还必须处理类型转换,这会带来所有固有的危险。
示例
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);
输出:
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)
【讨论】:
我使用 Object ... arguments 版本能够以通用方式执行方法。实际的接口使用不同的类型定义,因此您可以拥有 Test3以上是关于Java 8:带有可变参数的 Lambda的主要内容,如果未能解决你的问题,请参考以下文章