【问题标题】:Callable lambda expression with argument带参数的可调用 lambda 表达式
【发布时间】:2018-02-22 02:44:15
【问题描述】:

我正在尝试使用泛型实现“TaskExecutor”(我第一次尝试使用泛型)并使用 ExecutorService。

这是我的“TaskExecutor”类:

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;
    }
}

这是我的运行方式:

@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);
}

这是我的问题: 如何编写我的 Callable 以使其在一行中接受参数 i

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

例如期望的结果是:

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

如果我的代码有其他问题 - 请告诉我。

编辑

移除了可调用的实现

上面的代码是我真正想做的抽象

  • IntRange 确实是一组我从 SQL 获取数据的批次。可调用 真正实现了如何处理这些 SQL 批处理的逻辑。

EDIT2

毕竟我有以下解决方案:

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;
    }
}

以及运行方式:

   @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

更简单的运行方式:

   @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);
    }

我想我对最终的解决方案很满意。谢谢大家!

【问题讨论】:

  • 不使用Callable为什么还要实现?
  • @RealSkeptic 好的,知道了。这回答了如何摆脱 T call()
  • 有了新的编辑,它看​​起来更像是你做错了。如果你想干净地提供一个 Executor,你提交的 Callable&lt;&gt; 应该有它需要的所有信息。批次的迭代需要在外部完成。或者你把方法改成process(Collection&lt;Callable&lt;&gt;&gt;)
  • Callable 本质上是一个Supplier。您可以使用Function 提供参数并返回结果,但必须由执行者提供参数。然后你必须提供一个参数供应商……是的,它可以完成,但它不会很漂亮。
  • @lapkritinis 是的,这看起来很简单——如果你甚至需要一个类,只需 lambda 就足以产生输出。取决于i 的来源。

标签: java lambda callable


【解决方案1】:

这有点不可能。如果那是 lambda,则不能将变量传递给可调用对象。另一方面,您可以使用自己的特定对象来实现Callable 并为变量设置一个设置器:

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;
    }

}

用法:

@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);
}

或者,您可以在构造函数中设置参数而不是设置器。

【讨论】:

    【解决方案2】:

    首先,你根本不需要call(),你也不需要在这个类中实现Callable&lt;T&gt;,因为你从来没有这样使用它。

    要以您想要的方式创建Callable,您可以这样做:

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

    您实际上是在为 lambda 提供外部变量 i 作为参数。

    【讨论】:

    • 我看到它会返回我所询问的内容。只是想这个解决方案是否适用于我的“真实事物”
    • 但这会将“业务逻辑”转移到 TaskExecutor 中——这是我想要避免的事情
    • 是的,但是将来自 TaskExecutor 的 i 参数注入您的业务逻辑听起来就像您正在尝试做的那样。我可能误解了你的问题;也许您应该发布“期望的运行方式”而不是“期望的结果”。
    猜你喜欢
    • 2014-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-02
    • 2017-12-07
    • 2018-07-31
    相关资源
    最近更新 更多