¿Por qué una lambda cambia las sobrecargas cuando genera una excepción de tiempo de ejecución?

enga en cuenta que la introducción es un poco larga pero es un acertijo interesant

Tengo este código:

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(); }
        }
    }
}

Estoy tratando de agregar tareas a una cola y ejecutarlas en orden. Esperaba que los 3 casos invocaran eladd(Runnable) método; sin embargo, lo que realmente sucede es que el caso 2 se interpreta como unaSupplier<CompletionStage<Void>> que arroja una excepción antes de devolver unCompletionStage entonces el bloque de código "esto nunca debería suceder" se activa y el caso 3 nunca se ejecuta.

Confirmé que el caso 2 invoca el método incorrecto al pasar por el código utilizando un depurador.

Por qué no es laRunnable método invocado para el segundo caso?

l parecer, este problema solo ocurre en Java 10 o superior, así que asegúrese de realizar la prueba en este entorno.

ACTUALIZA: De acuerdo aJLS §15.12.2.1. Identificar métodos potencialmente aplicables y mas especificamenteJLS §15.27.2. Lambda Body parece que() -> { throw new RuntimeException(); } pertenece a la categoría de "compatible con el vacío" y "compatible con el valor". Claramente, hay cierta ambigüedad en este caso, pero ciertamente no entiendo por quéSupplier es más apropiado para una sobrecarga queRunnable aquí. No es que el primero arroje alguna excepción que el segundo no.

No entiendo lo suficiente sobre la especificación para decir qué debería suceder en este caso.

I archivé un informe de error que es visible enhttps: //bugs.openjdk.java.net/browse/JDK-820849

Respuestas a la pregunta(5)

Su respuesta a la pregunta