By schemaboi


2019-05-15 12:48:48 8 Comments

My question is about InterruptedException, which is thrown from the Thread.sleep method. While working with ExecutorService I noticed some weird behaviour that I don't understand; 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. But when I am trying to change the loop condition and replace "true" with some variable like this:

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

The compiler constantly complains that InterruptedException has to be handled. Can someone explain to me why this happens, and why if the condition is set to true the compiler ignores the InterruptedException?

2 comments

@Roman Puchkovskiy 2019-05-26 08:02:35

Briefly

ExecutorService has both submit(Callable) and submit(Runnable) methods.

  1. In the first case (with the while (true)), both submit(Callable) and submit(Runnable) match, so the compiler has to choose between them
    • submit(Callable) is chosen over submit(Runnable) because Callable is more specific than Runnable
    • Callable has throws Exception in call(), so it is not necessary to catch an exception inside it
  2. In the second case (with the while (tasksObserving)) only submit(Runnable) match, so the compiler chooses it
    • Runnable has no throws declaration on its run() method, so it is a compilation error to not catch the exception inside the run() method.

The full story

Java Language Specification describes how the method is chosen during program compilation in $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
  2. Choose the Most Specific Method ($15.12.2.5) from the methods found on the first step.

Let's analyze the situation with 2 submit() methods in two code snippets provided by the OP:

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).

Identify Potentially Applicable Methods

First, the compiler has to identify the potentially applicable methods: $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.

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:

The arity of the target type's function type is the same as the arity of the lambda expression.

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).

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).

Let's note that in both cases, the lambda is a block 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. 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.

$15.27.2 defines what a void-compatible-block and value-compatible-block are.

A block lambda body is void-compatible if every return statement in the block has the form 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;.

Let's look at $14.21, paragraph about while loop:

A while statement can complete normally iff at least one of the following is true:

The while statement is reachable and the condition expression is not a constant expression (§15.28) with value true.

There is a reachable break statement that exits the while statement.

In borh cases, lambdas are actually block 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); also it has no return statements, hence the first lambda is value-compatible.

At the same time, there are no return statements at all, so it is also void-compatible. So, in the end, in the first case, the lambda is both void- and value-compatible.

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. But it is still a void-compatible block because it contains no return statements.

The intermediate result is that in the first case the lambda is both a void-compatible block and a value-compatible block; 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; in the second case, the lambda will only be potentially compatible with 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. 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):

If e is an explicitly typed lambda expression (§15.27.1), then one of the following is true:

R2 is void.

First of all,

A lambda expression with zero parameters is explicitly typed.

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. This means that between submit(Callable) and submit(Runnable) in the first case the method with Callable will be chosen.

As for the second case, there we only have one potentially applicable method, submit(Runnable), so it is chosen.

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. 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.

@Marco R. 2019-05-15 13:20:04

The reason for this is that these invocations are, in fact, invocations to two different overloaded methods, taking two different type of arguments, each type with different exception handling specifications:

  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); and in the second case of your problem converts the lambda into a Runnable functional interface (invoking therefore the second overloaded method).

Although both functional interfaces don't take arguments, Callable<?> returns a value and it throws Exception (very important!):

  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 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 <CHECKED_EXCEPTION>().
  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 rethrown.
  3. Since the lambda is not handling the exception, then the compiler defaults to assume that these exception(s) must be rethrown.
  4. Safely infers that this lambda must match a functional interface that throws Exception.
  5. Since Callable<?> is the only matching functional interface available for the overloaded methods available, it selects it, converts the lambda into a Callable<?> and creates an invocation reference to the submit(Callable<?>) overloaded method.

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. The compiler dismisses Callable<?> as a matching functional interface for the lambda, since Callable does declare throwing exceptions. (a)
  4. Selects Runnable as the remaining 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.

(a) The compiler has no impeding reason not to default to convert always all lambdas to Callable<?> (and ease inner complications), other than having Callable<?> be a more restrictive functional interface than available, viable, alternatives (i.e. Runnable). This behavior is in line with the principle of 'being as restrictive as one MUST; but as unrestrictive as one CAN'.

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

EDIT (Response to @Roman Correction):

My answer is correct. The reason why the compiler decides to compile the first lambda from the question as a Callable<T> and the second one as a Runnable is because of their IMPLICIT DECLARED EXCEPTIONS FEATURES. The following is even a simpler example of this to bring the point home:

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

    // LAMBDA COMPILED INTO A 'Runnable'
    Executors.newSingleThreadExecutor().submit(() -> {                        });

So, I correctly answered the question originally asked - case closed.

Now, in a more general sense, it is obviously true that ALL SIGNATURE FEATURES of the lambdas are considered by the compiler to determine the functional interface to compile ANY lambda into (including argument types and return type); but this is not the case of the question; as it is clearly shown in the previous simpler example.

Regarding the example you based your entire argument:

    Executors.newSingleThreadExecutor().submit(() -> {
        while (true) {
            Thread.sleep(5000);
        }
    });

despite your claims...

For example, the following construct will NOT compile

The compiler just cannot tell whether it is a Runnable or a Callable; it can be any of them.

...this example DOES indeed compile. The compiler CAN tell that the lambda complies with the signature of a Callable<T>. In fact, that example is the same as the very first one of the question, so I don't know what you are talking about.

Finally, the examples you included thereafter ARE NOT EQUIVALENT with the examples presented in the question. You added return statements; which changes the question, because it adds features to the lambdas that are not present in the original question (i.e. the return type of the lambdas to discriminate which functional interface to compile it into) and which requires a different answer; but AGAIN that was nowhere in the question; and what's worse now I see your new comments in the question asking for modifications/edits to the question to fit your answer.

@VGR 2019-05-15 14:07:22

I think you have reversed Runnable and Callable. The first code block does not require catching of InterruptedException because the lambda is interpreted as a Callable. Also, I do not understand your explanation of why one lambda is interpreted as a Callable but not the other one; both are equally likely to throw InterruptedException.

@Marco R. 2019-05-15 14:22:55

You are right, I accidentally swapped them relative to the question when working them out the more focus examples. Regarding interpretation to Callable, it is a compiler optimization, I'll elaborate in the answer.

@gaurav 2019-05-15 15:22:09

@MarcoR. flawless answer! Kudos!

@Ertai87 2019-05-15 19:55:14

@MarcoR. This answer is great and informative! One question though (as a reader of this question purely for knowledge's sake): Why can we safely and without loss of generality convert Thread.sleep (in the OP) to throw new Exception (in your answer)? As far as I'm aware, Thread.sleep does not always throw an exception, and thus it occurs to me that in either case the compiler should infer that both cases may or may not throw an exception based on system state at runtime. What am I missing?

@Marco R. 2019-05-15 21:23:43

That's a good question, it was just poor choice of words on my part; it is not as much as the body of the lambda may/will throw exception(s) as it is that the body of the lambda is equivalent to declaring throwing exception(s) (which must be catch or rethrown). I edited the answer to be more precise around this potential confusion. Thanks for the heads up, lmk if it still seems unclear or if you have any other question.

@Ertai87 2019-05-16 15:50:32

@MarcoR. I'm still somewhat unclear. Would it be correct to say the following? Since in the first case, in the original post, we have a while true (which we always enter), and the body of the while true throws an exception (in the sense that it consists of one or more method calls whose method signatures have throws clauses, not in the sense that they always throw an exception in all cases), then the compiler infers that the matching overloaded interface for the entire lambda must also throw an exception; in the latter case because the loop is a while condition, then it is not guaranteed...

@Ertai87 2019-05-16 15:52:18

... that every control flow of that lambda contains any method call whose method signature contains a throws clause, and therefore it fits to the overloaded interface which does not throw an exception by default. This would be kind of a very strange and complex compiler optimization, I suppose, which is why I find it somewhat hard to believe, although it would certainly not be the strangest thing I've heard of Java doing.

@Marco R. 2019-05-16 17:03:16

@Ertai87 The compiler doesn't infer runtime behavior, so it's not as much as inferring what the code is going to do, as much as what it declares of potentially doing. In the Callable case, the lambda body can be correctly interpreted of declaring as a whole capable of throwing exceptions (which must be handled of declare rethrowing). In the Runnable case, the lambda body CANNOT be generalized as declaring as a whole of 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.

@Ertai87 2019-05-16 17:26:19

Neat! Thanks, very informative!

@Roman Puchkovskiy 2019-05-25 11:23:27

Sorry guys, but throws clause has nothing to do with the functional interface selection. What matters here is whether any return is reachable from the point of view of the compiler, and which flavor it is (with or without an operand). See my answer for details.

@Marco R. 2019-05-25 13:21:52

@Roman - I just added a section in the answer to address your incorrect 'corrections'. I DID answer the question correctly as it was asked - don't change the question to invalidate my answer.

@Roman Puchkovskiy 2019-05-25 20:02:00

@MarcoR.I'm flattered by the amount of the attention, but let's stay on the topic. I apologize if I hurt your feelings, I just wanted (and still want) to solve the mistery. Could you please provide me (and the rest of us) with a link to Java Language Specification where it says that throws declaration has anything to do with method selection during compile time?

Related Questions

Sponsored Content

24 Answered Questions

[SOLVED] Java 8 Lambda function that throws exception?

  • 2013-08-12 23:16:36
  • Triton Man
  • 266604 View
  • 412 Score
  • 24 Answer
  • Tags:   java lambda java-8

2 Answered Questions

[SOLVED] Mockito test a void method throws an exception

31 Answered Questions

[SOLVED] Why doesn't RecyclerView have onItemClickListener()?

13 Answered Questions

[SOLVED] Missing return statement in a non-void method compiles

  • 2013-05-28 10:32:48
  • c.P.u1
  • 8869 View
  • 189 Score
  • 13 Answer
  • Tags:   java c# oop

22 Answered Questions

[SOLVED] Why doesn't Java allow overriding of static methods?

26 Answered Questions

[SOLVED] Why does this go into an infinite loop?

10 Answered Questions

[SOLVED] Why doesn't JUnit provide assertNotEquals methods?

  • 2009-07-08 07:46:43
  • Chris B
  • 159711 View
  • 415 Score
  • 10 Answer
  • Tags:   java junit assert

13 Answered Questions

[SOLVED] I get exception when using Thread.sleep(x) or wait()

  • 2010-07-27 10:28:27
  • vincent low
  • 882738 View
  • 340 Score
  • 13 Answer
  • Tags:   java sleep

3 Answered Questions

[SOLVED] Thread.sleep() in a while loop

  • 2012-01-09 17:46:54
  • Steve Ferguson
  • 27748 View
  • 21 Score
  • 3 Answer
  • Tags:   java while-loop sleep

Sponsored Content