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