Home > front end >  Pass parameter to lambda expression - Java
Pass parameter to lambda expression - Java

Time:02-15

My program requires that I accept a user input and, based on this input, a method is to be carried out. My basic thoughts are described well by the following question/answer:

How to call a method stored in a HashMap? (Java)

To do this, I have created an array of lambda expressions:

public final Runnable[] userCommandMethods = {

        () -> userCommand1(),
        () -> userCommand2(),
    };

And an array of keys:

public final String[] userCommandKeys = {

        commandKey1,
        commandKey2,
    };

Which are joined to create a HashMap using the following method:

public Map<String, Runnable> mapArrays (String[] array1, Runnable[] array2) {

        Map<String, Runnable> mappedArrays = new HashMap<String, Runnable>();

        for (int i = 0; i < array1.length; i   ) {

            mappedArrays.put(array1[i], array2[i]);
        }
        return mappedArrays;
    }

When I attempt to run a method by using myHashMap.get(userInput).run(); it works perfectly, provided none of the methods in userCommandMethods require input parameters.

My question:

How would I pass an input parameter (specifically a Hash Map) into the methods contained within userCommandMethods?

When the userCommand1() method takes an input parameter, but the lambda expression does not, I get the following error:

The method userCommand1(Map<String, String>) in the type ProgramCommands is not applicable for the arguments ()

However, when I do pass a parameter to the method, it states that it cannot be resolved to a variable.

Edit: to elaborate:

When the userCommand1() method takes no arguments:

public void userCommand1 () {

      // Do some stuff
}

It works perfectly fine. However, I am unsure how to use the lambda expressions if the method does take an input parameter:

public void userCommand1 (Map<String, String> myMap) {

      // Do some stuff
}

CodePudding user response:

You just need to choose another functional interface (not Runnable).

For example, if your methods all take a String parameter, you should use Consumer<String>. If they take a String and an int, then you should use BiConsumer<String, Integer>. If your methods need more than 2 parameters, you need to create your own functional interface. For an example, see my answer here.

// use a list instead of an array, because arrays don't work well with generic types 
public final List<Consumer<String>> userCommandMethods = List.of(
    x -> userCommand1(x),
    x -> userCommand2() // it's fine if the method takes fewer parameters
);

Instead of run, you would call accept, which is what Consumer and BiConsumer's single abstraction method is called.

Note that you can also use the method reference syntax. If userCommand1 is static, x -> userCommand1(x) can be rewritten as SomeClass::userCommand1 where SomeClass is the enclosing class of userCommand1. If userCommand1 is non static, it can be rewritten as this::userCommand1.

You don't need to build the map from two arrays. You can use ofEntries and entry to write the entries inline.

private final Map<String, Consumer<String>> someMap = Map.ofEntries(
    Map.entry("foo", SomeClass::userCommand1),
    Map.entry("bar", SomeClass::userCommand2),
    Map.entry("baz", SomeClass::userCommand3),
    // and so on
)

CodePudding user response:

You are using Runnable interface that takes no argument on input:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Instead, you can define your custom interface and consume it.

As a simple example:

@FunctionalInterface
public interface RunnableWithArg {    
    void apply(String t) throws RuntimeException;    
}

And implementation may look like:

public class RunnableTest {

    //also fine:
    //public final RunnableWithArg[] userCommandMethods = { t -> this.userCommand1(t), t -> this.userCommand2(t) };
    
    public final RunnableWithArg[] userCommandMethods = { this::userCommand1, this::userCommand2 };

    public String commandKey1 = "commandKey1";
    public String commandKey2 = "commandKey2";

    public final String[] userCommandKeys = { commandKey1, commandKey2, };

    public Map<String, RunnableWithArg> mapArrays(String[] array1, RunnableWithArg[] array2) {

        Map<String, RunnableWithArg> mappedArrays = new HashMap<>();

        for (int i = 0; i < array1.length; i  ) {

            mappedArrays.put(array1[i], array2[i]);
        }
        return mappedArrays;
    }

    public void userCommand1(String data) {
        System.out.println("userCommand1 called with "   data);
    }

    public void userCommand2(String data) {
        System.out.println("userCommand2 called with "   data);
    }
    
    public void test()
    {
        var fncMap = mapArrays(userCommandKeys, userCommandMethods);
        
        for(String key: fncMap.keySet())
        {
            var fnc = fncMap.get(key);
            fnc.apply(key);
        }
    }
}

And of course you can also define some generic types of "@FunctionalInterface" like this, so you can use it for both taking input and returning some output of generic types:

@FunctionalInterface
public interface AbcFunction<T, R> {

    R apply(T t) throws AbcException;

    static <T> Function<T, T> identity() {
        return t -> t;
    }    
}

CodePudding user response:

Is this something you are thinking of?


interface Command<T> {
   public void run(T arg);
}

class SayHelloCommand implements Command<String>{
   public void run(String name){
      System.out.println("hello "   name);
   }
}

class CountCommand implements Command<Integer>{
   public void run(Integer limit){
      for(int i=0; i<=limit; i  )
        System.out.println(i);
   }
}

public class Main{
    public static void main(String[] args) {
        Command[] commands = new Command[3];
        commands[0] = new SayHelloCommand();
        commands[1] = new CountCommand();
        
        commands[0].run("Joe");
        commands[1].run(5);
    }
}
  • Related