如何在lambda被提供给调用方法的结果中分配变量时,如何在lambda中使用变量?

时间:2014-09-14 01:29:34

标签: java lambda java-8

我有方法TaskManager.newRepeatingTask(Runnable r, long delay, long interval)并返回UUID。我为该方法返回的内容分配了一个UUID变量,我想在Runnable内部使用该变量。我将如何有效地完成这项工作,或者我将在这里完成的工作?

UUID id = TaskManager.newRepeatingTask(() -> {
    for (int i = 0; i < profileButtons.length; i++) {
        GuiButton button = profileButtons[i];
        button.move(-1, 0);
    }
    toMove--;
    if (toMove == 0) {
        // id: "The variable may not have been initialized"
        TaskManager.cancelTask(id);
    }
}, 30L, 0L);

4 个答案:

答案 0 :(得分:5)

当您不确定哪个将首先到达时,使用java.util.concurrent.CompletableFuture(Java 8中的新增功能)在线程或任务之间传输值。方法如下:

CompletableFuture<UUID> id = new CompletableFuture<>();
id.complete(TaskManager.newRepeatingTask(() -> {
    for (int i = 0; i < profileButtons.length; i++) {
        GuiButton button = profileButtons[i];
        button.move(-1, 0);
    }
    toMove--;
    if (toMove == 0) {
        TaskManager.cancelTask(id.join());
    }
}, 30L, 0L));

join()方法将收集并返回complete()方法提供的值。如果尚未调用complete()join()将会阻止,直到CompletableFuture为止。 TaskManager.newRepeatingTask()在内部处理所有同步和内存可见性问题。

正如其他人所说,这有点人为。重复任务取消自身的更常规方法是让它返回一个布尔值,指示是否应该重新安排或取消它。要执行此操作,请更改Supplier<Boolean>以取Runnable而不是{{1}}。

答案 1 :(得分:4)

您可以让TaskManager.newRepeatingTask接受Consumer&lt; UUID&gt;。然后使用当时已知的UUID创建一个runnable。

所以你在内部做这样的事情:

//inside newRepeatingTask(consumer:Consumer<UUID> ...)
Runnable r = new Runnable() {
    public UUID uuid;
    @Override
    public void run() {
        consumer.accept(uuid); //calls the lambda
    }
};
r.uuid = getNextUUID(); //do whatever here
//add to your presumed list of runnables

现在你可以这样做:

UUID id = TaskManager.newRepeatingTask((UUID id) -> {
    TaskManager.cancelTask(id);
    //probably do something better with id
}, 30L, 0L);
//LOOK MA, this code is DRY

答案 2 :(得分:3)

我认为你在这里必须要复杂一点。我想到的第一件事是:

  • 使用可设置(例如公开)字段Runnable
  • 创建uuid(匿名)子类
  • 使用您的runnable对象调用newRepeatingTask并获取UUID
  • 使用setter在Runnable上设置UUID

那将是:

Runnable r = new Runnable() {
    public UUID uuid;

    @Override
    public void run() {
        for (int i = 0; i < profileButtons.length; i++) {
            GuiButton button = profileButtons[i];
            button.move(-1, 0);
        }
        toMove--;
        if (toMove == 0) {
            // id: "The variable may not have been initialized"
            TaskManager.cancelTask(uuid);
        }
    }
}
UUID id = TaskManager.newRepeatingTask(r, 30L, 0L);
r.uuid = id;

抱歉,但我认为你不得不放弃lambda:&#39;(

重要提示:正如@Dici所述,如果runnable在newRepeatingTask内运行,则可能会出现一些同步问题。你可以考虑AlexanderBrevig建议的选项,它允许你在runnable上调用run()之前设置id。

答案 3 :(得分:2)

我的第一个解决方案,如果我可以控制TaskManager,则会更改它,以便它还将UUID参数传递给回调或使用另一种控制方法 - 然后使用该方法的结果没有实际意义。

然而,如果我没有那么..


(编辑:我已被告知在Java 8中处理此问题的正确方法是使用CompletableFuture - 请参阅Stuarts的答案。)

另一种方法是使用“可变参考包装器”,如Holder<T>(或T[]或自己创建)来模拟可变绑定。然后,

Holder<UUID> idRef = new Holder<UUID>(); // Effectively final

idRef.value = TaskManager.newRepeatingTask(() -> {
    for (int i = 0; i < profileButtons.length; i++) {
        GuiButton button = profileButtons[i];
        button.move(-1, 0);
    }
    toMove--;
    if (toMove == 0) {
        UUID id = idRef.value;
        TaskManager.cancelTask(id);
    }
}, 30L, 0L);

与Runnable-with-UUID方法类似,此在ID的分配和lambda / Runnable 中的潜在用法之间存在潜在的竞争条件,如果该任务在不同的线程上运行。 (如果稍后在同一个线程上运行,则不需要应用同步问题;如果在同一个线程上立即运行,则在lambda中永远不会观察到UUID。)

除非立即调用Runnable,否则应用外部/包装方法调用本身(以及适用代码周围)的共享同步应该处理。出于保证可见性原因(即使没有“竞争条件”),如果采用这种方法并且任务可以在不同的线程上执行,则应该无论如何进行同步或等效。

Holder<UUID> idRef = new Holder<UUID>();

synchronized(idRef) {
  idRef.value = TaskManager.newRepeatingTask(() -> {
      for (int i = 0; i < profileButtons.length; i++) {
          GuiButton button = profileButtons[i];
          button.move(-1, 0);
      }
      toMove--;
      if (toMove == 0) {
          // id: "The variable may not have been initialized"
          synchronized(idRef) {
            UUID id = idRef.value;
            TaskManager.cancelTask(id);
          }
      }
  }, 30L, 0L);
}