I am trying to understand why the compiler is unable to resolve the bar
method call. I would expect bar(Xyz::new)
to always select bar(Supplier)
as bar(T extends Xyz)
can never match due to the upper bound on Xyz
.
public <T extends Xyz> void foo(T s) {}
public <T extends Xyz> void bar(T s) {}
public <T extends Xyz> void bar(Supplier<T> s) {}
public void example() {
foo(Xyz::new); // not valid (does not extend Xyz)
bar((Supplier<Xyz>) Xyz::new); // valid (explicitly a Supplier)
bar(Xyz::new); // ambiguous - but only one method is valid?
}
public static class Xyz {}
If bar(T)
is not applicable, even when alone (as shown with foo(T)
), then surely the only option is bar(Supplier)
making this a non-ambiguous overload.
Why is the bar
call ambiguous, especially when the foo
and bar(T)
calls are not valid resolutions themselves?
Runnable example of above code: https://www.jdoodle.com/ia/kqP
Java ambiguous method call This ambiguous method call error always comes with method overloading where compiler fails to find out which of the overloaded method should be used. Suppose we have a java program like below.
The ambiguities are those issues that are not defined clearly in the Java language specification. The different results produced by different compilers on several example programs support our observations.
Ambiguous Invocation. □ Sometimes there may be two or more possible. matches for an invocation of a method, but the. compiler cannot determine the most specific match. This is referred to as ambiguous invocation.
You're right that a smarter compiler should be able to resolve this unambiguously.
The way Java resolves method invocations is complex. It's defined by the JLS, and I make it 7500 words purely to determine how to resolve a method. Pasted into a text editor, it was 15 pages.
The general approach is:
I don't understand anywhere close to all of the details and how it pertains to your specific case. If you care to dive into it then I've already linked the full spec. Hopefully this explanation is good enough for your purposes:
Ambiguousness is determined at step 2.6, but there is still a further appropriateness check at step 3. Your foo
method must be failing at step 3. Your bar
method never makes it that far because the compiler still considers both methods to be valid possibilities. A human can make the determination that the non-appropriateness resolves the ambiguity, but that's not order the compiler does things. I could only speculate why - performance might be a factor.
Your code is operating at the intersection of generics, overloading and method references, all three of which were introduced at different times; it's not massively surprising to me that the compiler would struggle.
Your problem is mostly a problem of type inference than a problem of ambiguous method:
public <T extends Xyz> void bar(T s) {} // bar(Xyz)
public void bar(String s) {}
public <T extends Zyx> void bar(T s) {} // bar(Zyx)
public <T extends Xyz> void bar(Supplier<T> s) {}
public static class Xyz {}
public static class Zyx {}
If you use:
bar(new Xyz()); // ok
bar("a"); // ok
bar(new Zyx()); // ok
bar((Supplier<Xyz>) Xyz::new); // ok
bar(Xyz::new); // ambiguous
You get this error (tried with Java 17) which is not about the lambda, but about the type T
: cannot infer type-variable(s) T
both method <T#1>bar(T#1) in Example and method <T#2>bar(Supplier<T#2>) in Example match
where T#1,T#2 are type-variables:
T#1 extends Zyx declared in method <T#1>bar(T#1)
T#2 extends Xyz declared in method <T#2>bar(Supplier<T#2>)
Example.java:18: error: incompatible types: cannot infer type-variable(s) T
Java is not smart enough to find the concrete type T is this case, and you have to help it:
Example.<Xyz>bar(Xyz::new);
I tried to look into the JLS, driven by Michael answer, and the section that should better answer your question is the 18.5.1. Invocation Applicability Inference.
I had the same kind of errors frequently occurring with Java 7 and Collections:
public static <T extends Zyx> void bar(java.util.List<T> s) {} // bar(Zyx)
public static <T extends Zyx> void bar(T s) {} // bar(List)
bar(new Zyx());
bar(java.util.Collections.emptyList());
The worse of it being that Eclipse was having no trouble, while javac failed.
I suppose that in the case of lambdas, the compiler does not infer the type T from the "Xyz".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With