Java compiler: How can two methods with the same name and different signatures match a method call?

According to the JLS §15.12.2.2:

An argument expression is considered pertinent to applicability for a potentially applicable method m unless it has one of the following forms:

  • An implicitly typed lambda expression1.
  • An inexact method reference expression2.
  • [...]

Therefore:

verify("bar", tokens.get("foo", e -> String.valueOf(e)));

an implicitly typed lambda expression e -> String.valueOf(e) is skipped from the applicability check during overload resolution - both verify(...) methods become applicable - hence the ambiguity.

In comparison, here are some examples that will work, because the types are specified explicitly:

verify("bar", tokens.get("foo", (Function<Object, String>) e -> String.valueOf(e)));

verify("bar", tokens.get("foo", (Function<Object, String>) String::valueOf));

1 - An implicitly typed lambda expression is a lambda expression, where the types of all its formal parameters are inferred.
2 - An inexact method reference - one with multiple overloads.


There are multiple implementations of String.valueOf(...) with different arguments. The compiler does not know which one you want to call. The compiler is not capable of seeing that all the possible methods actually return a String and therefore it does not really matter which method is called. Since the compiler does not know what the return type will be it cannot infer a proper Function<...,...> as the type of the expression and it therefore cannot understand wether you will have a Function or something else at hand and therefore cannot tell if you want to call the get method with a Function or a Class.


If you instead of String::valueOf use e -> String.valueOf(e) then the compiler can infer a little more but it will still not understand that you will always return a String and will therefore interpret it as Function<Object, Object> which your verify method then has a problem with.


e -> e.toString I do not understand fully, I do not see why the compiler is incapable of inferring String as a return type here. It infers Object and does the exact same thing as in the previous case. If you split the operation into

String s = tokens.get("baz", e -> e.toString());
verify("bat", s);  // line 21

then it works because the compiler can infer the generic R from the type of s. The same way it works by explicitly specifying R:

verify("bat", tokens.<String>get("baz", e -> e.toString()));  // line 21

String.class the compiler easily understands that you want to call the get(Class) method.


Object::toString makes sense to work since the compiler knows this will be a Function<Object, String>.