Consider the following two classes and interface:
public class Class1 {}
public class Class2 {}
public interface Interface1 {}
Why does the second call to mandatory
invoke the overloaded method with Class2
, if getInterface1
and Interface1
have no relationship with Class2
?
public class Test {
public static void main(String[] args) {
Class1 class1 = getClass1();
Interface1 interface1 = getInterface1();
mandatory(getClass1()); // prints "T is not class2"
mandatory(getInterface1()); // prints "T is class2"
mandatory(class1); // prints "T is not class2"
mandatory(interface1); // prints "T is not class2"
}
public static <T> void mandatory(T o) {
System.out.println("T is not class2");
}
public static <T extends Class2> void mandatory(T o) {
System.out.println("T is class2");
}
public static <T extends Class1> T getClass1() {
return null;
}
public static <T extends Interface1> T getInterface1() {
return null;
}
}
I understand that Java 8 broke compatibility with Java 7:
$ /usr/lib/jvm/java-8-openjdk-amd64/bin/javac -source 1.7 -target 1.7 *java; /usr/lib/jvm/java-8-openjdk-amd64/bin/java Test
warning: [options] bootstrap class path not set in conjunction with -source 1.7
1 warning
T is not class2
T is not class2
T is not class2
T is not class2
And with Java 8 (also tested with 11 and 13):
$ /usr/lib/jvm/java-8-openjdk-amd64/bin/javac *java; /usr/lib/jvm/java-8-openjdk-amd64/bin/java Test
T is not class2
T is class2
T is not class2
T is not class2
The rules of type inference have received a significant overhaul in Java 8, most notably target type inference has been much improved. So, whereas before Java 8 the method argument site did not receive any inference, defaulting to erased type (Class1
for getClass1()
and Interface1
for getInterface1()
), in Java 8 the most specific applicable type is inferred. The JLS for Java 8 introduced a new chapter Chapter 18. Type Inference that's missing in JLS for Java 7.
The most specific applicable type for <T extends Interface1>
is <X extends RequiredClass & BottomInterface>
, where RequiredClass
is a class required by a context, and BottomInterface
is a bottom type for all interfaces (including Interface1
).
Note: Each Java type can be represented as SomeClass & SomeInterfaces
. Since RequiredClass
is sub-type of SomeClass
, and BottomInterface
is sub-type of SomeInterfaces
, X
is sub-type of every Java type. Therefore, X
is a Java bottom type.
X
matches both public static <T> void mandatory(T o)
and public static <T extends Class2> void mandatory(T o)
methods signatures since X
is Java bottom type.
So, according to §15.12.2, mandatory(getInterface1())
calls the most specific overloading of mandatory()
method, which is public static <T extends Class2> void mandatory(T o)
since <T extends Class2>
is more specific than <T>
.
Here is how you can explicitly specify getInterface1()
type parameter to make it return the result that matches public static <T extends Class2> void mandatory(T o)
method signature:
public static <T extends Class2 & Interface1> void helper() {
mandatory(Test.<T>getInterface1()); // prints "T is class2"
}
The most specific applicable type for <T extends Class1>
is <Y extends Class1 & BottomInterface>
, where BottomInterface
is a bottom type for all interfaces.
Y
matches public static <T> void mandatory(T o)
method signature, but it doesn't match public static <T extends Class2> void mandatory(T o)
method signature since Y
doesn't extend Class2
.
So mandatory(getClass1())
calls public static <T> void mandatory(T o)
method.
Unlike with getInterface1()
, you can't explicitly specify getClass1()
type parameter to make it return the result that matches public static <T extends Class2> void mandatory(T o)
method signature:
java: interface expected here
↓
public static <T extends Class1 & C̲l̲a̲s̲s̲2> void helper() {
mandatory(Test.<T>getClass1());
}