简体   繁体   English

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

[英]Thread.sleep inside infinite while loop in lambda doesn't require 'catch (InterruptedException)' - why not?

My question is about InterruptedException , which is thrown from the Thread.sleep method. 我的问题是关于InterruptedException ,它是从Thread.sleep方法抛出的。 While working with ExecutorService I noticed some weird behaviour that I don't understand; 在使用ExecutorService我注意到一些我不理解的奇怪行为; here is what I mean: 这就是我的意思:

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

With this code, the compiler doesn't give me any error or message that InterruptedException from Thread.sleep should be caught. 使用此代码,编译器不会给我任何错误或消息,应该捕获Thread.sleep中的InterruptedException But when I am trying to change the loop condition and replace "true" with some variable like this: 但是当我试图改变循环条件并用这样的变量替换“true”时:

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

The compiler constantly complains that InterruptedException has to be handled. 编译器经常抱怨必须处理InterruptedException Can someone explain to me why this happens, and why if the condition is set to true the compiler ignores the InterruptedException? 有人可以向我解释为什么会发生这种情况,以及为什么如果条件设置为true,编译器会忽略InterruptedException?

The reason for this, is that these invocations are in fact, invocations to two different overloaded methods available in ExecutorService ; 原因是这些调用实际上是对ExecutorService可用的两个不同重载方法的调用; each of these methods taking a single argument of different types: 这些方法中的每一个都采用不同类型的单个参数:

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

Then what happens is that the compiler is converting the lambda in the first case of your problem into a Callable<?> functional interface (invoking the first overloaded method); 然后会发生的是编译器在第一种情况下将lambda转换为Callable<?>功能接口(调用第一个重载方法); and in the second case of your problem converts the lambda into a Runnable functional interface (invoking therefore the second overloaded method), requiring because of this to handle the Exception thrown; 在你的问题的第二种情况下,将lambda转换为Runnable功能接口(因此调用第二个重载方法),因此需要处理抛出的Exception ; but not in the previous case using the Callable . 但不是在之前的情况下使用Callable

Although both functional interfaces don't take any arguments, Callable<?> returns a value : 虽然两个函数接口都不带任何参数,但Callable<?> 返回一个值

  1. Callable: V call() throws Exception; 可调用: V call() throws Exception;
  2. Runnable: public abstract void run(); Runnable: public abstract void run();

If we switch to examples that trim the code to the relevant pieces (to easily investigate just the curious bits) then we can write, equivalently to the original examples: 如果我们切换到将代码修剪为相关部分的示例(以便轻松调查好奇的部分),那么我们可以编写,与原始示例等效:

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

With these examples, it may be easier to observe that the reason why the first one is converted to a Callable<?> , while the second one is converted to a Runnable is because of compiler inferences . 通过这些示例,可能更容易观察到第一个转换为Callable<?> ,而第二个转换为Runnable的原因是编译器推断

In both cases, the lambda bodies are void-compatible , since every return statement in the block has the form return; 在这两种情况下,lambda体都是void兼容的 ,因为块中的每个return语句都有返回形式return; .

Now, in the first case, the compiler does the following: 现在,在第一种情况下,编译器执行以下操作:

  1. Detects that all execution paths in the lambda declare throwing checked exceptions (from now on we will refer as 'exception' , implying only 'checked exceptions' ). 检测到lambda中的所有执行路径都声明抛出已检查的异常 (从现在开始,我们将引用为'exception' ,仅暗示'checked exceptions' )。 This includes the invocation of any method declaring throwing exceptions and the explicit invocation to throw new <CHECKED_EXCEPTION>() . 这包括调用声明抛出异常的任何方法和显式调用以throw new <CHECKED_EXCEPTION>()
  2. Concludes correctly that the WHOLE body of the lambda is equivalent to a block of code declaring throwing exceptions; 正确地得出结论,lambda的WHOLE主体等同于声明抛出异常的代码块; which of course MUST be either: handled or re-thrown. 当然必须是:处理或重新抛出。
  3. Since the lambda is not handling the exception, then the compiler defaults to assume that these exception(s) must be re-thrown. 由于lambda不处理异常,因此编译器默认假定必须重新抛出这些异常。
  4. Safely infers that this lambda must match a functional interface cannot complete normally and therefore is value-compatible . 安全地推断该lambda必须与功能接口匹配不能complete normally ,因此是价值兼容的
  5. Since Callable<?> and Runnable are potential matches for this lambda, the compiler selects the most specific match (to cover all scenarios); 由于Callable<?>Runnable是此lambda的潜在匹配项,因此编译器会选择最具体的匹配项(以涵盖所有方案); which is the Callable<?> , converting the lambda into an instance of it and creating an invocation reference to the submit(Callable<?>) overloaded method. 这是Callable<?> ,将lambda转换为它的实例并创建对submit(Callable<?>)重载方法的调用引用。

While, in the second case, the compiler does the following: 而在第二种情况下,编译器执行以下操作:

  1. Detects that there may be execution paths in the lambda that DO NOT declare throwing exceptions (depending on to-be-evaluated logic ). 检测lambda中可能存在执行路径,这些路径不会声明抛出异常(取决于待评估逻辑 )。
  2. Since not all execution paths declare throwing exceptions, the compiler concludes that the body of the lambda is NOT NECESSARILY equivalent to a block of code declaring throwing exceptions - compiler doesn't care/pay attention if some portions of the code do declare that they may, only if the whole body does or not. 由于并非所有执行路径都声明抛出异常,编译器得出结论,lambda的主体并不等同于声明抛出异常的代码块 - 如果代码的某些部分确实声明它们可能,则编译器不关心/注意,只有整个身体做了。
  3. Safely infers that the lambda is not value-compatible ; 安全地推断lambda不是价值兼容的 ; since it MAY complete normally . 因为它可以 complete normally
  4. Selects Runnable (as it is the only available fitting functional interface for the lambda to be converted into) and creates an invocation reference to the submit(Runnable) overloaded method. 选择Runnable (因为它是唯一可用于转换为lambda的拟合函数接口),并创建对submit(Runnable)重载方法的调用引用。 All this coming at the price of delegating to the user, the responsibility of handling any Exception s thrown wherever they MAY occur within portions of the lambda body. 所有这一切都在委托给用户的价格到来,处理任何责任Exception š抛出无论他们在哪里拉姆达身体部分内发生。

This was a great question - I had a lot of fun chasing it down, thanks! 这是一个很好的问题 - 我有很多乐趣追逐它,谢谢!

Briefly 简要地

ExecutorService has both submit(Callable) and submit(Runnable) methods. ExecutorService具有submit(Callable)submit(Runnable)方法。

  1. In the first case (with the while (true) ), both submit(Callable) and submit(Runnable) match, so the compiler has to choose between them 在第一种情况下(使用while (true) ), submit(Callable)submit(Runnable)匹配,因此编译器必须在它们之间进行选择
    • submit(Callable) is chosen over submit(Runnable) because Callable is more specific than Runnable submit(Callable)是在submit(Runnable)选择的,因为CallableRunnable 更具体
    • Callable has throws Exception in call() , so it is not necessary to catch an exception inside it Callablecall() throws Exception ,因此没有必要在其中捕获异常
  2. In the second case (with the while (tasksObserving) ) only submit(Runnable) match, so the compiler chooses it 在第二种情况下(使用while (tasksObserving) )只submit(Runnable)匹配,所以编译器选择它
    • Runnable has no throws declaration on its run() method, so it is a compilation error to not catch the exception inside the run() method. Runnable没有throws声明它run()方法,因此它是一个编译错误,以抓不住里面异常run()方法。

The full story 全文

Java Language Specification describes how the method is chosen during program compilation in $15.2.2 : Java语言规范描述了在$ 15.2.2中的程序编译期间如何选择方法:

  1. Identify Potentially Applicable Methods ( $15.12.2.1 ) which is done in 3 phases for strict, loose and variable arity invocation 确定可能适用的方法( $ 15.12.2.1 ),分为3个阶段,用于严格,松散和可变的arity调用
  2. Choose the Most Specific Method ( $15.12.2.5 ) from the methods found on the first step. 从第一步中找到的方法中选择最具体的方法( $ 15.12.2.5 )。

Let's analyze the situation with 2 submit() methods in two code snippets provided by the OP: 让我们用OP提供的两个代码片段中的2个submit()方法分析情况:

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

and

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

(where tasksObserving is not a final variable). (其中tasksObserving不是最终变量)。

Identify Potentially Applicable Methods 确定可能适用的方法

First, the compiler has to identify the potentially applicable methods : $15.12.2.1 首先,编译器必须确定可能适用的方法 :$ 15.12.2.1

If the member is a fixed arity method with arity n, the arity of the method invocation is equal to n, and for all i (1 ≤ i ≤ n), the i'th argument of the method invocation is potentially compatible , as defined below, with the type of the i'th parameter of the method. 如果成员是具有arity n的固定arity方法,则方法调用的arity等于n,并且对于所有i(1≤i≤n),方法调用的第i个参数可能兼容的 ,如定义的那样下面,使用方法的第i个参数的类型。

and a bit further in the same section 并在同一部分中进一步说明

An expression is potentially compatible with a target type according to the following rules: 根据以下规则,表达式可能与目标类型兼容

A lambda expression (§15.27) is potentially compatible with a functional interface type (§9.8) if all of the following are true: 如果满足以下所有条件,则lambda表达式(第15.27节)可能与函数接口类型(第9.8节)兼容:

The arity of the target type's function type is the same as the arity of the lambda expression. 目标类型的函数类型的arity与lambda表达式的arity相同。

If the target type's function type has a void return, then the lambda body is either a statement expression (§14.8) or a void-compatible block (§15.27.2). 如果目标类型的函数类型具有void返回,则lambda主体是语句表达式(§14.8)或void兼容块(§15.27.2)。

If the target type's function type has a (non-void) return type, then the lambda body is either an expression or a value-compatible block (§15.27.2). 如果目标类型的函数类型具有(非void)返回类型,则lambda主体是表达式或值兼容块(第15.27.2节)。

Let's note that in both cases, the lambda is a block lambda. 让我们注意,在这两种情况下,lambda都是一个块lambda。

Let's also note that Runnable has void return type, so to be potentially compatible with Runnable , a block lambda must be void-compatible block . 我们还要注意Runnable具有void返回类型,因此要与Runnable 兼容 ,块lambda必须是void兼容块 At the same time, Callable has a non-void return type, so to be potentially comtatible with Callable , a block lambda must be value-compatible block . 同时, Callable具有非void返回类型,因此可以与Callable 一起使用,块lambda必须是与值兼容的块

$15.27.2 defines what a void-compatible-block and value-compatible-block are. $ 15.27.2定义了void-compatible-blockvalue-compatible-block是什么。

A block lambda body is void-compatible if every return statement in the block has the form return; 如果块中的每个return语句都具有return;形式,则块lambda体是void兼容的return; .

A block lambda body is value-compatible if it cannot complete normally (§14.21) and every return statement in the block has the form return Expression; 如果块lambda body无法正常完成(第14.21节)并且块中的每个return语句都具有return Expression;的形式,则它是值兼容的return Expression; .

Let's look at $14.21, paragraph about while loop: 让我们看看$ 14.21,关于while循环的段落:

A while statement can complete normally iff at least one of the following is true: 如果至少满足下列条件之一,则while语句可以正常完成:

The while statement is reachable and the condition expression is not a constant expression (§15.28) with value true. while语句是可访问的,条件表达式不是值为true的常量表达式(第15.28节)。

There is a reachable break statement that exits the while statement. 有一个可到达的break语句退出while语句。

In borh cases, lambdas are actually block lambdas. 在borh案例中,lambdas实际上是阻止lambdas。

In the first case, as it can be seen, there is a while loop with a constant expression with value true (without break statements), so it cannot complete normallly (by $14.21); 在第一种情况下,可以看出,有一个带有常量表达式的while循环,其值为true (没有break语句),因此它无法正常完成($ 14.21); also it has no return statements, hence the first lambda is value-compatible . 它也没有返回语句,因此第一个lambda是值兼容的

At the same time, there are no return statements at all, so it is also void-compatible . 同时,根本没有return语句,因此它也是无效的 So, in the end, in the first case, the lambda is both void- and value-compatible . 因此,最后, 在第一种情况下,lambda是无效的和价值兼容的

In the second case, the while loop can complete normally from the point of view of the compiler (because the loop expression is not a constant expression anymore), so the lambda in its entirety can complete normally , so it is not a value-compatible block . 在第二种情况下, while循环可以从编译器的角度正常完成 (因为循环表达式不再是常量表达式),因此lambda的整体可以正常完成 ,因此它不是 值兼容的块 But it is still a void-compatible block because it contains no return statements. 它仍然是一个与void兼容的块,因为它不包含return语句。

The intermediate result is that in the first case the lambda is both a void-compatible block and a value-compatible block ; 中间结果是在第一种情况下,lambda既是void兼容块又是值兼容块 ; in the second case it is only a void-compatible block . 在第二种情况下,它只是一个与空隙兼容的块

Recalling what we noted earlier, this means that in the first case, the lambda will be potentially compatible both with Callable and Runnable ; 回顾一下我们之前提到的,这意味着在第一种情况下,lambda 可能CallableRunnable 兼容 ; in the second case, the lambda will only be potentially compatible with Runnable . 在第二种情况下,lambda只能与Runnable 兼容

Choose the Most Specific Method 选择最具体的方法

For the first case, the compiler has to choose between the two methods because both are potentially applicable . 对于第一种情况,编译器必须在两种方法之间进行选择,因为两种方法都可能适用 It does so using the procedure called 'Choose the Most Specific Method' and described in $15.12.2.5. 它使用名为“选择最具体的方法”的程序并在$ 15.12.2.5中描述。 Here is an excerpt: 这是一段摘录:

A functional interface type S is more specific than a functional interface type T for an expression e if T is not a subtype of S and one of the following is true (where U1 ... Uk and R1 are the parameter types and return type of the function type of the capture of S, and V1 ... Vk and R2 are the parameter types and return type of the function type of T): 如果T不是S的子类型且下列之一为真,则功能接口类型S比表达式e的功能接口类型T更具体(其中U1 ... Uk和R1是参数类型和返回类型捕获S的函数类型,V1 ... Vk和R2是T)函数类型的参数类型和返回类型:

If e is an explicitly typed lambda expression (§15.27.1), then one of the following is true: 如果e是显式类型的lambda表达式(第15.27.1节),则以下之一为真:

R2 is void. R2无效。

First of all, 首先,

A lambda expression with zero parameters is explicitly typed. 显式地键入具有零参数的lambda表达式。

Also, neither of Runnable and Callable is a subclass of one another, and Runnable return type is void , so we have a match: Callable is more specific than Runnable . 另外, RunnableCallable都不是彼此的子类, Runnable返回类型是void ,所以我们有一个匹配: CallableRunnable更具体 This means that between submit(Callable) and submit(Runnable) in the first case the method with Callable will be chosen. 这意味着在第一种情况下,在submit(Callable)submit(Runnable) ,将选择具有Callable的方法。

As for the second case, there we only have one potentially applicable method, submit(Runnable) , so it is chosen. 至于第二种情况,我们只有一种可能适用的方法, submit(Runnable) ,因此选择它。

So why does the change surface? 那么为什么变化表面呢?

So, in the end, we can see that in these cases different methods are chosen by the compiler. 因此,最后,我们可以看到,在这些情况下,编译器会选择不同的方法。 In the first case, the lambda is inferred to be a Callable which has throws Exception on its call() method, so that sleep() call compiles. 在第一种情况下,lambda被推断为Callable ,它在call()方法上throws Exception ,因此sleep()调用编译。 In the second case, it's Runnable which run() does not declare any throwable exceptions, so the compiler complains about an exception not being caught. 在第二种情况下,它是Runnablerun()不会声明任何可抛出的异常,因此编译器会抱怨没有被捕获的异常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM