Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 ambiguous method reference for generic class

The code below compiles and runs ok in Java 7 but fails to compile in Java 1.8.0 u25:

public class GenericTest {

    public static class GenericClass<T> {
        T value;

        public GenericClass(T value) {
            this.value = value;
        }
    }

    public static class SecondGenericClass<T> {
        T value;

        public SecondGenericClass(T value) {
            this.value = value;
        }
    }


    public static<T >void verifyThat(SecondGenericClass<T> actual, GenericClass<T> matcher) {
    }

    public static<T >void verifyThat(T actual, GenericClass<T> matcher) {
    }

    @Test
    public void testName() throws Exception {
        verifyThat(new SecondGenericClass<>(""), new GenericClass<>(""));
    }

}

The error message in Java 8 looks like this:

Error:(33, 9) java: reference to verifyThat is ambiguous
  both method <T>verifyThat(com.sabre.ssse.core.dsl.GenericTest.SecondGenericClass<T>,com.sabre.ssse.core.dsl.GenericTest.GenericClass<T>) in com.sabre.ssse.core.dsl.GenericTest and method <T>verifyThat(T,com.sabre.ssse.core.dsl.GenericTest.GenericClass<T>) in com.sabre.ssse.core.dsl.GenericTest match

I've reviewed all the changes between:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2
https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2

But I failed to notice the exact reason for this behaviour.

Edit:

Just to answer some comments, it's quite clear that the compiler in both Java 7 and 8 will be able to handle such invocations (with signatures similar to what's left after compile time type erasure:

public static void verifyThat(SecondGenericClass actual, GenericClass matcher) {
}

public static void verifyThat(Object actual, GenericClass matcher) {
}

@Test
public void testName() throws Exception {
    verifyThat(new SecondGenericClass<>(""), new GenericClass<>(""));
}

The bytecode generated for both generic methods, and erased is the same, and looks like this:

public static verifyThat(Lcom/sabre/ssse/core/dsl/GenericTest$SecondGenericClass;Lcom/sabre/ssse/core/dsl/GenericTest$GenericClass;)V
public static verifyThat(Ljava/lang/Object;Lcom/sabre/ssse/core/dsl/GenericTest$GenericClass;)V

Edit2:

Compilation under javac 1.8.0_40 fails with the same error

like image 932
xendoo Avatar asked Apr 07 '15 13:04

xendoo


2 Answers

JLS, chapter §15.12.2.5 Choosing the Most Specific Method is a hard read but contains an interesting summary:

The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time type error.

We can easily disprove this for your case with the following example:

GenericTest.<String>verifyThat( // invokes the first method
    new SecondGenericClass<>(""), new GenericClass<>(""));
GenericTest.<SecondGenericClass<String>>verifyThat( // invokes the second
    new SecondGenericClass<>(""), new GenericClass<>(null));

so there is no most specific method here, however, as the example shows, it is possible to invoke either method using arguments that make the other method inapplicable.

In Java 7 it was easier to make a method inapplicable due to the limited attempts (of the compiler) to find type arguments to make more methods applicable (aka limited type inference). The expression new SecondGenericClass<>("") had the type SecondGenericClass<String> inferred from its argument "" and that’s it. So for the invocation verifyThat(new SecondGenericClass<>(""), new GenericClass<>("")) the arguments had the type SecondGenericClass<String> and GenericClass<String> which made the method <T> void verifyThat(T,GenericClass<T>) inapplicable.

Note that there is an example of an ambiguous invocation which exhibits the ambiguity under Java 7 (and even Java 6): verifyThat(null, null); will provoke a compiler error when using javac.

But Java 8 has Invocation Applicability Inference (there we have a difference to JLS 7, an entirely new chapter…) which allows the compiler to choose type arguments which make a method candidate applicable (which works through nested invocations). You can find such type arguments for your special case, you can even find a type argument which fits both,

GenericTest.<Object>verifyThat(new SecondGenericClass<>(""), new GenericClass<>(""));

is unambiguously ambiguous (in Java 8), even Eclipse agrees on that. In contrast, the invocation

verifyThat(new SecondGenericClass<>(""), new GenericClass<String>(""));

is specific enough to render the second method inapplicable and invoke the first method, which gives us a hint about what’s going on in Java 7 where the type of new GenericClass<>("") is fixed as GenericClass<String> just like with new GenericClass<String>("").


The bottom line is, it’s not the choosing of the most specific method which changed from Java 7 to Java 8 (significantly), but the applicability due to the improved type inference. Once both methods are applicable, the invocation is ambiguous as neither method is more specific than the other.

like image 110
Holger Avatar answered Oct 06 '22 10:10

Holger


In resolving which method to use in the case where multiple methods are applicable, "...the types of an invocation's arguments cannot, in general, be inputs to the analysis." The Java 7 spec is missing this qualification.

If you substitute T in the second definition of verifyThat for SecondGenericClass the signatures match.

In other words, imagine attempting to call the second definition of verifyThat like this:

SecondGenericClass<String> t = new SecondGenericClass<String>("foo");
GenericTest.verifyThat(t, new GenericClass<String>("bar"));

At runtime, there would be no way to determine which version of verifyThat to call since the type of variable t is a valid substitution for both SecondGenericClass<T> and T.

Note that if Java had reified generics (and it will someday), in this example one method signature is not more specific than the other. Closing loopholes...

like image 41
Jonathan Schneider Avatar answered Oct 06 '22 09:10

Jonathan Schneider