Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Java know which overloaded method to call with lambda expressions? (Supplier, Consumer, Callable, ...)

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?

like image 831
Edward Avatar asked Sep 12 '25 18:09

Edward


1 Answers

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)

like image 160
Panagiotis Bougioukos Avatar answered Sep 15 '25 07:09

Panagiotis Bougioukos