Lambda中无限while循环内的Thread.sleep不需要'catch(InterruptedException)'-为什么不呢?

sch*_*boi 62 java lambda multithreading compilation functional-interface

我的问题是关于InterruptedException,它是从Thread.sleep方法中抛出的。在合作时,ExecutorService我注意到一些我不理解的怪异行为;这是我的意思:

ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(() -> {
        while(true)
        {
            //DO SOMETHING
            Thread.sleep(5000);
        }
    });
Run Code Online (Sandbox Code Playgroud)

有了这个代码,编译器不给我任何错误或消息InterruptedExceptionThread.sleep应该被抓。但是,当我尝试更改循环条件并用诸如此类的变量替换“ true”时:

ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(() -> {
        while(tasksObserving)
        {
            //DO SOMETHING
            Thread.sleep(5000);
        }
    });
Run Code Online (Sandbox Code Playgroud)

编译器不断抱怨InterruptedException必须处理。有人可以向我解释为什么会发生这种情况,为什么将条件设置为true则编译器会忽略InterruptedException?

Mar*_* R. 63

这样做的原因是,这些调用实际上是对ExecutorService;中可用的两种不同的重载方法的调用。这些方法中的每一个均采用不同类型的单个参数:

  1. <T> Future<T> submit(Callable<T> task);
  2. Future<?> submit(Runnable task);

然后发生的是,编译器将问题的第一种情况下的lambda转换为Callable<?>功能接口(调用第一个重载方法);在问题的第二种情况下,将lambda转换为Runnable功能接口(因此调用第二个重载方法),因此需要处理该Exception抛出的问题;但在以前的情况下不使用Callable

尽管两个功能接口均不接受任何参数,但Callable<?> 返回值

  1. 可致电: V call() throws Exception;
  2. 可运行的: public abstract void run();

如果我们切换到将代码修整到相关部分的示例(以便轻松地研究好奇的位),那么我们可以编写与原始示例等效的代码:

    ExecutorService executor = Executors.newSingleThreadExecutor();

    // LAMBDA COMPILED INTO A 'Callable<?>'
    executor.submit(() -> {
        while (true)
            throw new Exception();
    });

    // LAMBDA COMPILED INTO A 'Runnable': EXCEPTIONS MUST BE HANDLED BY LAMBDA ITSELF!
    executor.submit(() -> {
        boolean value = true;
        while (value)
            throw new Exception();
    });
Run Code Online (Sandbox Code Playgroud)

通过这些示例,可能更容易观察到第一个被转换为a Callable<?>,而第二个被转换为a Runnable的原因是由于编译器推断

在这两种情况下,lambda主体都是无效的,因为该块中的每个return语句都具有形式return;

现在,在第一种情况下,编译器将执行以下操作:

  1. 检测到lambda中的所有执行路径都声明抛出检查异常(从现在开始,我们将其称为“ exception”,仅表示“ checked exceptions”)。这包括调用任何声明抛出异常的方法以及对的显式调用throw new <CHECKED_EXCEPTION>()
  2. 正确的结论是,WHOLE拉姆达的身体相当于代码声明抛出异常块; 当然必须:处理或重新抛出。
  3. 由于lambda没有处理异常,因此编译器默认情况下假定必须重新抛出这些异常。
  4. 安全地推断此lambda必须与功能接口匹配complete normally,因此是值兼容的
  5. 由于Callable<?>Runnable是该lambda的潜在匹配项,因此编译器会选择最具体的匹配项(以涵盖所有情况)。这是Callable<?>,将lambda转换为其实例,并创建对submit(Callable<?>)重载方法的调用引用。

在第二种情况下,编译器执行以下操作:

  1. 检测到lambda中可能存在声明抛出异常的执行路径(取决于要评估的逻辑)。
  2. 由于不是所有的执行路径声明抛出异常,编译器的结论是,拉姆达的身体是不是必然等同于代码声明抛出异常块-编译不在乎/注意,如果代码的某些部分做宣布,他们可能,只有整个身体都可以。
  3. 安全地推断出lambda 值不兼容;自五月 以来complete normally
  4. 选择Runnable(因为它是要转换为lambda 的唯一可用的拟合功能接口),并创建对submit(Runnable)重载方法的调用引用。所有这些都是以委托给用户为代价的,即有责任处理可能在lambda主体内的任何位置Exception抛出的任何东西。

这是一个很好的问题-追逐它让我很开心,谢谢!

  • 我认为您已经颠倒了Runnable和Callable。第一个代码块不需要捕获InterruptedException,因为lambda被解释为Callable。另外,我不理解您对为什么一个lambda解释为Callable却不能解释为另一个Callable的解释。两者都有可能抛出InterruptedException。 (4认同)
  • 这是一个很好的问题,对我而言,这只是一个错误的选择。它不如可能会抛出异常的lambda主体那么多,而是因为lambda的主体等效于声明了throwing exception(s)(必须被捕获或重新抛出)。我编辑了答案,以更精确地解决这种潜在的混乱。感谢您的直觉,如果仍然不清楚或您还有其他问题,请致电lmk。 (3认同)
  • @MarcoR。这个答案是伟大的,翔实的!但是,有一个问题(纯粹出于知识的原因而作为该问题的读者):为什么我们可以安全而又不失一般性地将Thread.sleep(在OP中)转换为抛出新的Exception(在您的答案中)?据我所知,Thread.sleep并不总是抛出异常,因此在我看来,无论哪种情况,编译器都应根据运行时的系统状态推断两种情况都可能会或可能不会抛出异常。我想念什么? (2认同)
  • @ Ertai87编译器不会推断运行时的行为,因此它并不像推断代码将要做什么,而是推断它可能要做什么一样。在“可调用”的情况下,可以正确地解释lambda主体,使其具有能够引发异常的“整个声明” **(必须进行声明重新抛出)。在“ Runnable”情况下,lambda主体** CANNOT **可以泛化为**声明整个抛出异常** –如果代码的某些部分确实声明了它们,编译器将不在乎/注意只有整个身体都可以。 (2认同)