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
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.
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.
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.
The to
-method looks like this:
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. 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... .
PS: Having no need to use reflections is quite a good thing, since it can not fail and is quite save argument count wise.
In Java
you need to use an array like this.
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.
What I did was for my own use case was to define a helper method that 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.
Step #1: define the helper method:
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:
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:
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. 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.
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.
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.
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$1(Main.java:27)
at example.Main.test(Main.java:13)
at example.Main.main(Main.java:32)
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)
.