17

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.

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?

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.

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.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Martin Kersten
  • 5,127
  • 8
  • 46
  • 77
  • 13
    Short answer: no. Long answer: nooooooooo. (But then, what would you do with such a thing anyway?) – Louis Wasserman Aug 25 '15 at 16:46
  • Are all of your arguments the same type? If so have you looked into using varargs? What's a concrete example with context of what you're trying to do? – user4987274 Aug 25 '15 at 16:50
  • Louis sorry, I make an update for you (update2). – Martin Kersten Aug 25 '15 at 17:00
  • Is there any particular reason that you use a variable number of arguments but *cannot* use varargs? – user4987274 Aug 25 '15 at 19:11
  • To be fair, [yak shaving](http://sethgodin.typepad.com/seths_blog/2005/03/dont_shave_that.html) is a thing, and I think asking "what are you *really* trying to do?" is always a fair question... – dcsohl Aug 26 '15 at 12:35
  • There is no argument when it comes to DRY. You can do it or you cant do it. There is no half way through or any in between or partly involved with the concept. And yes asking that question is valid but sometimes I get a problem with it... . But you are right I apologise if this was inappropriate. I was not meaning this as an offense just as a certain stop before the 'why do you want to do this'-train starts. – Martin Kersten Aug 26 '15 at 16:19

6 Answers6

9

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.

Blake Yarbrough
  • 2,286
  • 1
  • 20
  • 36
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • I added an update to give you my current state. The Object [] args was exactly the wall I ran into and I worked around. Now I want to reduce the workaround as good as Java 8 allows to. – Martin Kersten Aug 25 '15 at 17:02
  • 1
    @MartinKersten Java has a static compiler, this means it can only compile for method calls known at compile time. If you don't know what the type is until runtime, you have to use reflection. – Peter Lawrey Aug 25 '15 at 17:05
  • I want to have variable arguments with lambdas. I know exactly what reflection is but in your example you have to provide a new Object [] {arg0, arg1} for every call which is verbose and I want to avoid that. So give your solution with {arg0, arg1} is the real deal I am looking for. Reflection wont help with this but is part of the solution :-). – Martin Kersten Aug 26 '15 at 09:48
  • @MartinKersten The way to deal with variable length arguments is to use an array as this could be different lengths. – Peter Lawrey Aug 26 '15 at 09:52
  • That would defeat the purpose of using lambdas to reduce the amount of verbosity and boilerplate. Currently I can do something like testNullPointer((a, b) -> new TestClass(to(a), to(b)), "a", null); as well as testNullPointer((a, b, c, d) -> new TestClass(to(a), to(b), to(c), to(d)), "a", null, 3, new Date()); That is exactly what I want to have with less interfaces (currently using a method overloaded 10 times and 11 different interfaces to support zero to 10 arguments) but using default methods I do not need reflections any more (which is very good). – Martin Kersten Aug 26 '15 at 11:48
4

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.

Martin Kersten
  • 5,127
  • 8
  • 46
  • 77
  • 2
    This is a practical solution, yet at the same time it kind of speaks to how Java can only be twisted to do this kind of thing. I wonder whether there's an elegant way. – matanster Apr 08 '17 at 04:29
3

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);
twm
  • 1,448
  • 11
  • 19
3

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.

Jimmy
  • 158
  • 1
  • 10
1

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.

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)
Clement Cherlin
  • 387
  • 6
  • 13
  • 1
    I use the Object ... arguments version to be able to execute methods in generic way. The actual interfaces are defined with different types so you can have a interface implementation of Test3 allowing you to use type safe versions of all those implementations. – Martin Kersten May 05 '20 at 19:23
  • 2
    You say that a helper method (like in my answer) is unnecessary, but in the example that you gave, it seems that test() is the helper method. – twm Jan 02 '21 at 15:38
  • 1
    @twm You're right, I should have written "overloaded methods" referring back to the OP's solution rather than "helper method" referring to your solution. I have edited my answer to correct it. – Clement Cherlin Jan 05 '21 at 15:29
0

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

user4987274
  • 211
  • 1
  • 3
  • Thanks for the code but I was looking for using lambdas. The invoker example is just what I called it since it is about invoking test methods generating test scenarios. – Martin Kersten Aug 25 '15 at 17:49
  • Note that lambdas are only a call-site construct, see http://stackoverflow.com/questions/13604703/how-do-i-define-a-method-which-takes-a-lambda-as-a-parameter-in-java-8. The invoker above *is* a lambda. printer just exists as a convenience unless the question you're having is how to define a lambda. – user4987274 Aug 25 '15 at 17:57
  • The invoker is not a lambda. A lambda is something in the form of (arg0, arg1) -> doSomething();. A lambda is translated (mapped) to a functional interface - usually thought to be annotated with @FunctionalInterface but it became optional, so any interface can be used if possible. So try your invoker as an argument to a method and use (arg0, arg1) -> System.println("message " + arg0 + ", " +arg1);. Do the same with (arg0, arg1, arg2) -> System.println("message " + arg0 + ", " + arg1 + ", " + arg2). – Martin Kersten Aug 25 '15 at 18:27
  • The `printer` declaration above is exactly equivalent to `Invoker printLambda = (Object ... args) -> { for (Object arg: args) { System.out.println(arg); } };`. Lambdas being defined with `->` is just syntactic sugar; the two are equivalent. – user4987274 Aug 25 '15 at 19:09