Потерпите меня, введение немного затянуто, но это интересная головоломка.
У меня есть этот код:
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.