Потерпите меня, введение немного затянуто, но это интересная головоломка.
У меня есть этот код:
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 метод Runnable для второго случая?
По-видимому, эта проблема возникает только на Java 10 или выше, поэтому обязательно проверяйте ее в этой среде.
ОБНОВЛЕНИЕ: Согласно JLS §15.12.2.1. Определить потенциально применимые методы и более конкретно JLS §15.27.2. Lambda Body кажется, что () → { throw new RuntimeException(); } () → { throw new RuntimeException(); } подпадает под категорию "совместимые с void" и "совместимые с стоимостью". Так что в этом случае есть определенная двусмысленность, но я, конечно, не понимаю, почему Supplier более подходит для перегрузки, чем Runnable. Это не так, как если бы первый выдавал исключения, которые последний не делает.
Я недостаточно разбираюсь в спецификации, чтобы сказать, что должно произойти в этом случае.
Я отправил отчет об ошибке, который отображается на https://bugs.openjdk.java.net/browse/JDK-8208490.