[SOLVED] How does Java know which overloaded method to call with lambda expressions? (Supplier, Consumer, Callable, …)

Issue

First off, I have no idea how to decently phrase the question, so this is up for suggestions.

Lets say we have following overloaded methods:

void execute(Callable<Void> callable) {
    try {
        callable.call();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

<T> T execute(Supplier<T> supplier) {
    return supplier.get();
}

void execute(Runnable runnable) {
    runnable.run();
}

Going off from this table, I got from another SO question

Supplier       ()    -> x
Consumer       x     -> ()
BiConsumer     x, y  -> ()
Callable       ()    -> x throws ex
Runnable       ()    -> ()
Function       x     -> y
BiFunction     x,y   -> z
Predicate      x     -> boolean
UnaryOperator  x1    -> x2
BinaryOperator x1,x2 -> x3

These are the results I get locally:

// Runnable -> expected as this is a plain void  
execute(() -> System.out.println()); 

// Callable -> why is it not a Supplier? It does not throw any exceptions..
execute(() -> null);

// Supplier -> this returns an Object, but how is that different from returning null?
execute(() -> new Object());

// Callable -> because it can throw an exception, right?
execute(() -> {throw new Exception();});

How does the compiler know which method to call?
How does it for example make the distinction between what’s a Callable and what’s a Runnable?

Solution

I believe I have found where this is described in official documentation, although a bit hard to read.

Here is mentioned:

15.27.3. Type of a Lambda Expression

Note that while boxing is not allowed in a strict invocation context,
boxing of lambda result expressions is always allowed – that is, the
result expression appears in an assignment context, regardless of the
context enclosing the lambda expression. However, if an explicitly
typed lambda expression is an argument to an overloaded method, a
method signature that avoids boxing or unboxing the lambda result is
preferred by the most specific check (§15.12.2.5).

and then here (15.12.2.5) is described analytically how the most specific method is chosen.

So according to this for example as described

One applicable method m1 is more specific than another applicable
method m2, for an invocation with argument expressions e1, …, ek, if
any of the following are true:

m2 is generic, and m1 is inferred to be more specific than m2 for
argument expressions e1, …, ek

So

// Callable -> why is it not a Supplier?
execute(() -> null);   <-- Callable shall be picked from 2 options as M2 is generic and M1 is inferred to be more specific

void execute(Callable<Void> callable) {  // <------ M1 
   try {
    callable.call();
  } catch (Exception e) {
      e.printStackTrace();
  }
}


 <T> T execute(Supplier<T> supplier) {  // <------ M2 is Generic
    return supplier.get();
 }

Why M1 is inferred to be more specific can be traced down from this process described here (18.5.4 More Specific Method Inference)

Answered By – Panagiotis Bougioukos

Answer Checked By – Jay B. (BugsFixing Admin)

Leave a Reply

Your email address will not be published.