Callable lambda expression with argument

13,823

Solution 1

This is sort of impossible. You cannot pass a variable to a callable, if that's a lambda. On the other hand, you can use your own specific object that implements Callable and has a setter for the variable:

public class CallableWithParam implements Callable<String> {

    // protected for subclassing call()
    // volatile for multi-threaded reasons
    protected volatile int param = 0;

    public void setParam(int param) {
        this.param = param;
    }

    @Override
    public String call() {
        return "my param is: " + param;
    }

}

Usage:

@Test
public void process() throws Exception {
    CallableWithParam callable = new CallableWithParam() {
    @Override
        public String call() {
             // an anonymous inner class is almost a lambda ;)
            return "my param is: " + param + "in subclass";
        }
    };
    callable.setParam(3);
    ExecuteAlerterTask<String> executeAlerterTask = new ExecuteAlerterTask<>();
    List<String> result = executeAlerterTask.process("TaskName", callable);
    result.forEach(System.out::println);
}

Alternatively, you can set the param in the constructor instead of the setter.

Solution 2

Firstly, you don't need call() at all, neither do you need to implement Callable<T> in this class because you never use it as such.

To create a Callable the way you want, you could do:

Callable<String> task; // from the parameter
int i; // from the loop
Callable<String> wrapper = () -> { return task.call() + " on " + i; }
executor.submit(wrapper);

You're essentially giving the lambda the outside variable i as a parameter.

Share:
13,823
lapkritinis
Author by

lapkritinis

Always chasing the best

Updated on June 20, 2022

Comments

  • lapkritinis
    lapkritinis almost 2 years

    I am trying to implement "TaskExecutor" with generics (my first attempt to use generics) and using ExecutorService.

    Here is my "TaskExecutor" class:

    public class ExecuteAlerterTask<T> {
        public List<T> process(String executorName, Callable<T> task) throws ExecutionException, InterruptedException {
            final ThreadFactory threadFactory = new ThreadFactoryBuilder()
                    .setNameFormat(executorName + "-%d")
                    .setDaemon(true)
                    .build();
            ExecutorService executor = Executors.newFixedThreadPool(10, threadFactory);
            Collection<Future<T>> futures = new ArrayList<>();
            IntStream.range(1, 10).forEach(i -> {
                Future<T> future = executor.submit(task);
                futures.add(future);
            });
    
            List<T> result = new ArrayList<>();
            for (Future<T> f : futures) {
                result.add(f.get());
            }
            executor.shutdown();
            return result;
        }
    }
    

    Here is my way to run it:

    @Test
    public void process() throws Exception {
        Callable<String> callable = () -> "Do something on ";
        ExecuteAlerterTask<String> executeAlerterTask = new ExecuteAlerterTask<>();
        List<String> result = executeAlerterTask.process("TaskName", callable);
        result.forEach(System.out::println);
    }
    

    And here is my question: How to write my Callable that it would accept argument i at a line:

    Future<T> future = executor.submit(task);
    

    E.g. desired result would be:

    Do something on 1
    Do something on 3
    Do something on 7
    Do something on 2
    <...etc...>
    

    If something else is wrong with my code - please let me know.

    EDIT

    Removed implements Callable

    Code above is abstraction of what I really want to do:

    • IntRange really is set of batches where I get data from SQL. Callable really implements logic how to process those SQL batches.

    EDIT2

    After all suggestions I have following solution:

    public class ExecuteAlerterTask<T> {
        public List<T> process(String executorName, Collection<Callable<T>> task) throws ExecutionException, InterruptedException {
            final ThreadFactory threadFactory = new ThreadFactoryBuilder()
                    .setNameFormat(executorName + "-%d")
                    .setDaemon(true)
                    .build();
            ExecutorService executor = Executors.newFixedThreadPool(10, threadFactory);
            Collection<Future<T>> futures = executor.invokeAll(task);
    
            List<T> result = new ArrayList<>();
            for (Future<T> f : futures) {
                result.add(f.get());
            }
            executor.shutdown();
            return result;
        }
    }
    

    And way to run it:

       @Test
        public void process() throws Exception {
            Collection<Callable<String>> tasks = new ArrayList<>();
            IntStream.range(1, 10).forEach(i -> {
                tasks.add(new Task(i).callable);
            });
    
            ExecuteAlerterTask<String> executeAlerterTask = new ExecuteAlerterTask<>();
            List<String> result = executeAlerterTask.process("TaskName", tasks);
            result.forEach(System.out::println);
        }
    
        private class Task {
            private int i;
            private Callable<String> callable = () -> "Doing something on i: " + i;
            private Task(int i) {
                this.i = i;
            }
        }
    

    EDIT3

    Even simpler way to run it:

       @Test
        public void process() throws Exception {
            Collection<Callable<String>> tasks = new ArrayList<>();
            IntStream.range(1, 10).forEach(i -> {
                tasks.add(() -> "Do something on i: " + i * 2);
            });
    
            ExecuteAlerterTask<String> executeAlerterTask = new ExecuteAlerterTask<>();
            List<String> result = executeAlerterTask.process("TaskName", tasks);
            result.forEach(System.out::println);
        }
    

    I guess I quite happy with final solution. Thanks all!