我有这段代码:
public class Testcase {
public static void main(String[] args){
EventQueue queue = new EventQueue();
queue.add(() -> System.out.println("case1"));
queue.add(() -> {
System.out.println("case2");
throw new IllegalArgumentException("case2-exception");});
queue.runNextTask();
queue.add(() -> System.out.println("case3-never-runs"));
}
private static class EventQueue {
private final Queue<Supplier<CompletionStage<Void>>> queue = new ConcurrentLinkedQueue<>();
public void add(Runnable task) {
queue.add(() -> CompletableFuture.runAsync(task));
}
public void add(Supplier<CompletionStage<Void>> task) {
queue.add(task);
}
public void runNextTask() {
Supplier<CompletionStage<Void>> task = queue.poll();
if (task == null)
return;
try {
task.get().
whenCompleteAsync((value, exception) -> runNextTask()).
exceptionally(exception -> {
exception.printStackTrace();
return null; });
}
catch (Throwable exception) {
System.err.println("This should never happen...");
exception.printStackTrace(); }
}
}
}
我正在尝试将任务添加到队列并按顺序运行它们。我原以为所有三种情况都会调用
add(Runnable)
方法; 然而,实际发生的是,第二种情况被解释为抛出异常后返回CompletionStage
的Supplier<CompletionStage<Void>>
,因此触发了“这不应该发生”的代码块,第三种情况从未运行。通过使用调试器逐步执行代码,我确认第二种情况正在调用错误的方法。 为什么第二种情况没有调用
Runnable
方法?
显然,这个问题只在Java 10或更高版本上发生,所以请务必在这个环境下进行测试。
更新:根据JLS §15.12.2.1.确定潜在适用的方法和更具体地说是JLS §15.27.2. Lambda Body,似乎() -> { throw new RuntimeException(); }
属于“void-compatible”和“value-compatible”两个类别。因此,在这种情况下存在一些歧义,但我当然不明白为什么Supplier
比Runnable
更适合重载。这并不意味着前者会抛出后者没有的任何异常。我对规范了解不够,无法确定此情况应该发生什么。
我已经提交了一个错误报告,可在https://bugs.openjdk.java.net/browse/JDK-8208490中查看。
Runnable
重载应该被选择的直觉。在我看来,例如() -> { throw new RuntimeException(); }
应该与返回值函数类型兼容,如果它是兼容的,那么实际选择哪个重载可能是任意的。(而且因为这个原因,即使对于某些人来说可能不直观,我怀疑这不是一个 bug。)将抛出异常的 lambda 传递给例如函数或供应商偶尔是有用的 (尤其是对于测试),如果它不兼容,我们将不得不使用一些非常愚蠢的解决方法。 - Radiodef() -> { for(;;); }
来获得相同的结果。 - Holger