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

后端 未结 2 1150
我寻月下人不归
我寻月下人不归 2021-02-01 00:50

My question is about InterruptedException, which is thrown from the Thread.sleep method. While working with ExecutorService I noticed some

2条回答
  •  后悔当初
    2021-02-01 00:57

    The reason for this, is that these invocations are in fact, invocations to two different overloaded methods available in ExecutorService; each of these methods taking a single argument of different types:

    1. Future submit(Callable 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); 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; but not in the previous case using the Callable.

    Although both functional interfaces don't take any arguments, Callable returns a value:

    1. Callable: V call() throws Exception;
    2. 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.

    In both cases, the lambda bodies are void-compatible, since every return statement in the block has the form 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'). This includes the invocation of any method declaring throwing exceptions and the explicit invocation to throw new ().
    2. Concludes correctly that the WHOLE body of the lambda is equivalent to a block of code declaring throwing exceptions; 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.
    4. Safely infers that this lambda must match a functional interface cannot complete normally and therefore is value-compatible.
    5. Since Callable and Runnable are potential matches for this lambda, the compiler selects the most specific match (to cover all scenarios); which is the Callable, converting the lambda into an instance of it and creating an invocation reference to the submit(Callable) overloaded method.

    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).
    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.
    3. Safely infers that the lambda is not value-compatible; since it MAY 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. All this coming at the price of delegating to the user, the responsibility of handling any Exceptions thrown wherever they MAY occur within portions of the lambda body.

    This was a great question - I had a lot of fun chasing it down, thanks!

提交回复
热议问题