Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the compiler choose this generic method with a class type parameter when invoked with an unrelated interface type?

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
like image 384
froque Avatar asked Dec 20 '19 18:12

froque


People also ask

How does a generic method differ from a generic type?

From the point of view of reflection, the difference between a generic type and an ordinary type is that a generic type has associated with it a set of type parameters (if it is a generic type definition) or type arguments (if it is a constructed type). A generic method differs from an ordinary method in the same way.

Which one of the following construct is used to create generic classes or methods?

We use <T> to create a generic class, interface, and method. The T is replaced with the actual type when we use it.

What is generic interface in Java?

Generic Interfaces in Java are the interfaces that deal with abstract data types. Interface help in the independent manipulation of java collections from representation details. They are used to achieving multiple inheritance in java forming hierarchies. They differ from the java class.


1 Answers

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());
}
like image 116
Bananon Avatar answered Oct 14 '22 06:10

Bananon