请耐心等待,介绍有点冗长,但这是一个有趣的谜题。
我有这个代码:
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(); }
}
}
}
我正在尝试将任务添加到队列中并按顺序运行它们。我原以为所有 3 个案例都会调用add(Runnable)
方法;然而,实际发生的情况是情况 2 被解释为Supplier<CompletionStage<Void>>
在返回之前抛出异常CompletionStage
因此“这不应该发生”代码块被触发,并且案例 3 永远不会运行。
我通过使用调试器单步执行代码来确认案例 2 调用了错误的方法。
为什么不是Runnable
第二种情况调用方法?
显然这个问题只发生在Java 10或更高版本上,所以一定要在这个环境下测试。
UPDATE: 根据JLS §15.12.2.1。确定潜在适用的方法 https://docs.oracle.com/javase/specs/jls/se10/html/jls-15.html#jls-15.12.2.1更具体地说JLS §15.27.2。拉姆达身体 https://docs.oracle.com/javase/specs/jls/se10/html/jls-15.html#jls-15.27.2看起来() -> { throw new RuntimeException(); }
属于“空兼容”和“值兼容”类别。很明显,在这种情况下存在一些含糊之处,但我当然不明白为什么Supplier
比过载更合适Runnable
这里。并不是说前者抛出了后者不会抛出的任何异常。
我对规范的了解不够,无法说出在这种情况下应该发生什么。
我提交了一份错误报告,可以在以下位置查看https://bugs.openjdk.java.net/browse/JDK-8208490 https://bugs.openjdk.java.net/browse/JDK-8208490
问题是有两种方法:
void fun(Runnable r)
and void fun(Supplier<Void> s)
.
还有一个表情fun(() -> { throw new RuntimeException(); })
.
将调用哪个方法?
根据JLS §15.12.2.1 https://docs.oracle.com/javase/specs/jls/se10/html/jls-15.html#jls-15.12.2.1,lambda 主体既兼容 void 又兼容值:
如果 T 的函数类型具有 void 返回,则 lambda 主体是语句表达式(第 14.8 节)或与 void 兼容的块(第 15.27.2 节)。
如果 T 的函数类型具有(非 void)返回类型,则 lambda 主体是表达式或值兼容块(第 15.27.2 节)。
所以这两种方法都适用于lambda表达式。
但是有两种方法,所以java编译器需要找出哪种方法更具体
In JLS §15.12.2.5 https://docs.oracle.com/javase/specs/jls/se10/html/jls-15.html#jls-15.12.2.5。它说:
如果满足以下所有条件,则对于表达式 e,函数式接口类型 S 比函数式接口类型 T 更具体:
以下之一是:
令RS为MTS的返回类型,适应MTT的类型参数,并令RT为MTT的返回类型。以下其中一项必须为真:
以下之一是:
RT 无效。
所以 S(即Supplier
) 比 T 更具体(即Runnable
) 因为方法的返回类型Runnable
is void
.
所以编译器选择Supplier
代替Runnable
.
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)