Search code examples
javagenericsoverload-resolution

Why is this Java generic method call ambiguous when only one method is valid when separate?


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


Solution

  • 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:

    1. Compile-Time Step 1: Determine Type to Search (no issue here)
    2. Compile-Time Step 2: Determine Method Signature
      1. Identify Potentially Applicable Methods
      2. Phase 1: Identify Matching Arity Methods Applicable by Strict Invocation
      3. Phase 2: Identify Matching Arity Methods Applicable by Loose Invocation
      4. Phase 3: Identify Methods Applicable by Variable Arity Invocation
      5. Choosing the Most Specific Method
      6. Method Invocation Type
    3. 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.