Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 688
Timothy Cole Avatar asked Nov 30 '21 21:11

Timothy Cole


People also ask

What is ambiguous method call in java?

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.

What does ambiguous mean in java?

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.

What is ambiguous invocation in java explain with example?

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.


Video Answer


2 Answers

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.

like image 81
Michael Avatar answered Oct 18 '22 20:10

Michael


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".

like image 18
NoDataFound Avatar answered Oct 18 '22 21:10

NoDataFound