12

In Java 1.8, the following lambda expression complies with both Runnable and Callable functional interfaces:

() -> {
    throw new RuntimeException("FIXME");
}

Still, if I submit it to an ExecutorService using a single-argument method, and ignore the return value (i. e. no type inference information is available), ExecutorService#submit(Callable) is chosen at compile time, unless I explicitly cast my lambda to Runnable.

How does the compiler choose between overloaded methods in the above case, provided that Runnable and Callable don't share any common hierarchy and most specific type rule doesn't apply here?

Bass
  • 4,977
  • 2
  • 36
  • 82
  • @Makoto That's exactly why overloading **does** happen here. – biziclop Oct 21 '15 at 14:48
  • @biziclop: I'm not sure why I got overriding and overloading mixed up there for a second. It must be the weather. – Makoto Oct 21 '15 at 14:49
  • Strangely enough my sample code of this chooses the `Runnable` overload. – biziclop Oct 21 '15 at 15:04
  • [Here's](http://ideone.com/xCTZVA) an example that demonstrates how strange things get. It is the exception throw that seems to swing it. – biziclop Oct 21 '15 at 15:10
  • 2
    @biziclop: there’s nothing strange about it. A lambda expression of the form `()->{}` without a `return value;` statement (and without `throw`) is `void`-compatible as it completes normally without returning a value, thus it can’t be a `Callable`. In contrast, a lambda expression which doesn’t complete normally *can* be value compatible. You can do the same with `()->{ while(true); }` which doesn’t complete normally. See [JLS §15.27.2](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.27.2) – Holger Oct 21 '15 at 15:30
  • @Holger I didn't mean to imply it's some mysterious thing, just that it's slightly counter-intuitive. Not the fact that `()->{}` is `void-compatible` (that's obvious), but rather the way `value-compatible` is defined. – biziclop Oct 21 '15 at 15:40
  • @biziclop: I’d rather say, it’s the *completion* topic that developers are often unaware, regardless of which context. For a non-lambda example, I guess some developers would be surprised that `int bla() { for(;;); }` is a valid method despite having no `return` and no `throw` statement… – Holger Oct 22 '15 at 08:54

2 Answers2

7

I believe this is because Callable declares a return type, and Runnable does not.

From the JLS section 15.12.2.5, the overload with the most specific type is chosen, if there is one unambiguously most specific. This is what it says about most specific functional interface types:

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

T is Runnable, S is Callable, Callable is more specific because its return type is not void, therefore Callable is chosen

Method overload resolution is very complicated, so there may be a bit I've missed, but I think this is why it chooses Callable

Community
  • 1
  • 1
thecoop
  • 45,220
  • 19
  • 132
  • 189
  • 1
    I think you should add that the lambda expression in the OP is considered explicitly typed because it has zero parameters. – RealSkeptic Oct 21 '15 at 15:04
  • @RealSkeptic: Neither `call` nor `run` require arguments, so how would that make one more explicitly typed than the other? – Makoto Oct 21 '15 at 15:07
  • Things seem to be a bit more complicated. If you don't throw the exception, `Runnable` is chosen. – biziclop Oct 21 '15 at 15:07
  • @biziclop even if you return a value in the lambda? – thecoop Oct 21 '15 at 15:08
  • 1
    @Makoto The rule thecoop quoted applies only to *explicitly typed* lambdas. So I was suggesting he should add the reason why the lambda in the OP is to be considered explicitly typed. – RealSkeptic Oct 21 '15 at 15:11
  • 2
    The funny thing is that the spec doesn’t say that R₁ has to be non-`void`, so if both functional types are `void` I can call either more specific than the other, depending on which is `S` and which is `T`… – Holger Oct 21 '15 at 15:26
  • @Holger that's only part of the spec, that applies specifically in this situation. There's lots of other rules about how to disambiguate lambda method calls that I haven't included – thecoop Oct 22 '15 at 08:33
  • @thecoop: of course, I’ve read the entire chapter before making a statement about what the spec *doesn’t say*. There are multiple points but it explicitly says that only one of them needs to be true. Nevertheless, I don’t think that anyone is going to implement a compiler settling on this small formal glitch just to be able to say “hey, it’s conforming to the spec”. It’s clear for a human reader, how it has to be interpreted… – Holger Oct 22 '15 at 08:46
4

Although @thecoop's answer is correct, there is another aspect of the lambda mechanism worth mentioning.

Lambdas with block bodies fall into two categories: void-compatible and value-compatible.

The lambda in the question is both, because it cannot complete normally (due to the unconditional exception throw) and all the return statements in there are both valueless and returning a value, seeing as there's no return statement at all.

Most lambdas however are either one or the other. And if the lambda is only void-compatible, then the lambda will only be compatible with Runnable.

What is slightly counter-intuitive (but logically correct) is that even though the lambda in the question never returns a value, it is classified as "always returning a value when there's a return".

Community
  • 1
  • 1
biziclop
  • 48,926
  • 12
  • 77
  • 104
  • “all the return statements in there are valueless” is a nonsensical statement when the are none. I could also say “all the return statements in there provide a value”. That’s not proving anything. And it’s not classified as “always returning a value”, but being *value-compatible* which just means, as it’s literally said, that it’s *compatible* with a function which is declared to return a value, not that it does return a value. – Holger Oct 22 '15 at 08:58
  • @Holger `A block lambda body is void-compatible if every return statement in the block has the form return;.` Don't blame me, this is the definition. And according to this definition something with no `return` in it is considered void-compatible. – biziclop Oct 22 '15 at 09:02
  • @Holger And for value-compatible: `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;.` Again, something that always throws an exception will be classed as value-compatible because every return statement in the block has the form `return Expression;` (and it can't complete normally either) – biziclop Oct 22 '15 at 09:03
  • 1
    But for some unknown reason, you are ignoring the same definition, when it comes to the value compatible lambda expression. There, you don’t write the sentence “all the return statements in there provide a value”. It’s still nonsensical to write it that way, esp. when you are not referring to the formal definition (expecting your readers to know it, renders your explanation obsolete). It would make more sense to write that there is *no contradicting* `return` statement. Then it should be clear to everyone, that without `return` statements, they can’t contradict neither form. – Holger Oct 22 '15 at 09:14
  • @Holger I do link to the definition in the previous paragraph. The difference is that in a regular `void` method, a `return;` is always implied, we're used to that. Methods that are supposed to return a value but never do are very rare compared to that (usually they are methods that throw `UnsupportedOperationException`), so even though the two definitions are formally symmetrical, one goes slightly against intuition. But I'll try to reword it to make it clear. – biziclop Oct 22 '15 at 09:26