TLDR:
The cases that fail to compile, fail to compile because of two main reasons:
- the lambdas have a statement expression (a method call in this case) as their bodies, making them compatible with both the
Consumer<T> and Function<T, R> overloads,
- the lambdas are also implicitly typed, making them not pertinent to applicability, so overload resolution is unable to decide between the overloads.
Let's go through the overload resolution steps in the spec to see where exactly this fails :)
First, let's determine the potentially applicable methods. For both x -> _void() and x -> _one(), both overloads are potentially applicable. This is because both lambda expressions are congruent to the function types of both Function<T, R> and Consumer<T>. The important condition is:
If the lambda parameters are assumed to have the same types as the
function type's parameter types, then:
- If the function type's result is void, the lambda body is either a statement expression (§14.8) or a void-compatible block.
- If the function type's result is a (non-void) type R, then either i) the lambda body is an expression that is compatible with R in an
assignment context, or ii) the lambda body is a value-compatible block, and each result expression (§15.27.2) is compatible with R in an assignment context.
(Also notice that for the cases that compile, exactly one of the methods is potentially applicable.)
Then we try to resolve the method to invoke using strict invocation. Loose and variable arity invocation are not very relevant here, so if this phase fails, the whole thing fails. Notice that at the start of that section, the spec defines "pertinent to applicability", and both x -> _void() and x -> _one() are not pertinent to applicability. This will be important.
We then reach:
If m is a generic method and the method invocation does not provide explicit type arguments, then the applicability of the method is inferred as specified in §18.5.1.
According to §18.5.1, to determine the applicability of a method wrt to a call, you first add inference bounds according to the arguments and type parameters. Then you reduce and incorporate the bounds. If there are no false bounds (which are produced when you have conflicting bounds) in the result, then the method is applicable. The relevant point here is that arguments that are not pertinent to applicability are not considered when adding those bounds:
To test for applicability by strict invocation:
If k ≠ n, or if there exists an i (1 ≤ i ≤ n) such that ei is
pertinent to applicability (§15.12.2.2) and either i) ei is a
standalone expression of a primitive type but Fi is a reference type,
or ii) Fi is a primitive type but ei is not a standalone expression of
a primitive type; then the method is not applicable and there is no
need to proceed with inference.
Otherwise, C includes, for all i (1 ≤ i ≤ k) where ei is pertinent to
applicability, ‹ei → Fi θ›.
So the only bounds that are added are those from the type parameters. They obviously are not going to disagree/conflict with each other and produce a false bound, since they are independent.
So again, both methods are applicable.
When there are more than one applicable method, we of course choose the most specific method. The process for doing this for generic methods is described here. It's quite long so I won't quote it here. In principle, it is similar to how §18.5.1 works - add some type bounds, if they agree with each other (no false), then one method is more specific than the other. In this case, however, the implicitly typed lambdas cause a false bound to be added :(
Now knowing this, you can basically make it work the way you want by using explicitly typed lambdas, which are pertinent to applicability.
f((Integer x) -> _one()); // (2)
f((Integer x) -> _void()); // (1)