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
CodePudding user response:
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:
- Compile-Time Step 1: Determine Type to Search (no issue here)
- Compile-Time Step 2: Determine Method Signature
- Identify Potentially Applicable Methods
- Phase 1: Identify Matching Arity Methods Applicable by Strict Invocation
- Phase 2: Identify Matching Arity Methods Applicable by Loose Invocation
- Phase 3: Identify Methods Applicable by Variable Arity Invocation
- Choosing the Most Specific Method
- Method Invocation Type
- Compile-Time Step 3: Is the Chosen Method Appropriate?
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.
CodePudding user response:
Your problem is mostly a problem of type inference that 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".