Bear with me, the introduction is a bit long-winded but this is an interesting puzzle.
I have this code:
public class Testcase {
public static void m
First things first:
The key point is that overloading methods or constructors with different functional interfaces in the same argument position causes confusion. Therefore, do not overload methods to take different functional interfaces in the same argument position.
Joshua Bloch, - Effective Java.
Otherwise, you'll need a cast to indicate the correct overloading:
queue.add((Runnable) () -> { throw new IllegalArgumentException(); });
^
The same behavior is evident when using an infinite loop instead of a runtime exception:
queue.add(() -> { for (;;); });
In the cases shown above, the lambda body never completes normally, which adds to the confusion: which overload to choose (void-compatible or value-compatible) if the lambda is implicitly typed? Because in this situation both methods become applicable, for example you can write:
queue.add((Runnable) () -> { throw new IllegalArgumentException(); });
queue.add((Supplier>) () -> {
throw new IllegalArgumentException();
});
void add(Runnable task) { ... }
void add(Supplier> task) { ... }
And, like stated in this answer - the most specific method is chosen in case of ambiguity:
queue.add(() -> { throw new IllegalArgumentException(); });
↓
void add(Supplier> task);
At the same time, when the lambda body completes normally (and is void-compatible only):
queue.add(() -> { for (int i = 0; i < 2; i++); });
queue.add(() -> System.out.println());
the method void add(Runnable task) is chosen, because there is no ambiguity in this case.
As stated in the JLS §15.12.2.1, when a lambda body is both void-compatible and value-compatible, the definition of potential applicability goes beyond a basic arity check to also take into account the presence and shape of functional interface target types.