为什么lambda会在抛出运行时异常时更改重载? [英] Why does a lambda change overloads when it throws a runtime exception?

查看:164
本文介绍了为什么lambda会在抛出运行时异常时更改重载?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

跟我说,介绍有点啰嗦,但这是一个有趣的难题。

Bear with me, the introduction is a bit long-winded but this is an interesting puzzle.

我有这个代码:

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被解释为供应商< CompletionStage< Void>> 在返回 CompletionStage之前抛出异常所以这应该永远不会发生代码块被触发而案例3永远不会运行。

I am trying to add tasks onto a queue and run them in order. I was expecting all 3 cases to invoke the add(Runnable) method; however, what actually happens is that case 2 gets interpreted as a Supplier<CompletionStage<Void>> that throws an exception before returning a CompletionStage so the "this should never happen" code block gets triggered and case 3 never runs.

我确认案例2通过步进调用了错误的方法使用调试器通过代码。

I confirmed that case 2 is invoking the wrong method by stepping through the code using a debugger.

为什么第二次调用 Runnable 方法case?

Why isn't the Runnable method getting invoked for the second case?

显然这个问题只发生在Java 10或更高版本上,因此请务必在此环境下进行测试。

Apparently this issue only occurs on Java 10 or higher, so be sure to test under this environment.

更新:根据JLS§15.12.2.1。确定可能适用的方法,更具体地说,JLS§15.27.2。 Lambda Body 似乎() - > {throw new RuntimeException(); } 属于void-compatible和value-compatible的类别。显然,在这种情况下存在一些含糊之处,但我当然不明白为什么供应商 Runnable 这里。这并不是说前者抛出后者没有的任何异常。

UPDATE: According to JLS §15.12.2.1. Identify Potentially Applicable Methods and more specifically JLS §15.27.2. Lambda Body it seems that () -> { throw new RuntimeException(); } falls under the category of both "void-compatible" and "value-compatible". So clearly there is some ambiguity in this case but I certainly don't understand why Supplier is any more appropriate of an overload than Runnable here. It's not as if the former throws any exceptions that the latter does not.

我对规范说不清楚,说明在这种情况下应该发生什么。

I don't understand enough about the specification to say what should happen in this case.

我提交了一份错误报告,可在 https://bugs.openjdk.java.net/browse/JDK-8208490

I filed a bug report which is visible at https://bugs.openjdk.java.net/browse/JDK-8208490

推荐答案

首先,根据§15.27.2表达式:

() -> { throw ... }

是否无效兼容,兼容,因此兼容(§15.27.3供应商< CompletionStage< Void>>

Is both void-compatible, and value-compatible, so it's compatible (§15.27.3) with Supplier<CompletionStage<Void>>:

class Test {
  void foo(Supplier<CompletionStage<Void>> bar) {
    throw new RuntimeException();
  }
  void qux() {
    foo(() -> { throw new IllegalArgumentException(); });
  }
}

(看它编译)

其次,根据§15.12.2.5 供应商< T> (其中 T 是引用类型)比 Runnable 更具体:

Second, according to §15.12.2.5 Supplier<T> (where T is a reference type) is more specific than Runnable:

让:


  • S := 供应商< T>

  • T := Runnable

  • e := () - > {throw ...}

  • S := Supplier<T>
  • T := Runnable
  • e := () -> { throw ... }

这样:


  • MTs := T get() ==> Rs : = T

  • MTt := void run() ==> Rt := void

  • MTs := T get() ==> Rs := T
  • MTt := void run() ==> Rt := void

并且:


  • S 不是的超接口或子接口code> T

  • MTs MTt 具有相同的类型参数(无)

  • 没有正式参数,因此项目符号3也是如此

  • e 是显式类型的lambda表达式, Rt void

  • S is not a superinterface or a subinterface of T
  • MTs and MTt have the same type parameters (none)
  • No formal parameters so bullet 3 is also true
  • e is an explicitly typed lambda expression and Rt is void

这篇关于为什么lambda会在抛出运行时异常时更改重载?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆