Perché il compilatore sceglie questo metodo generico con un parametro di tipo classe quando viene invocato con un tipo di interfaccia non correlato?


11

Considera le seguenti due classi e interfacce:

public class Class1 {}
public class Class2 {}
public interface Interface1 {}

Perché la seconda chiamata per mandatoryinvocare il metodo sovraccaricato con Class2, se getInterface1e Interface1non ha alcuna relazione 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;
    }
}

Capisco che Java 8 ha rotto la compatibilità con 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

E con Java 8 (testato anche con 11 e 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

1
In conclusione: il sovraccarico del metodo in Java porta così tante sorprese, che dovrebbe essere usato con estrema cura. Discriminare due sovraccarichi solo con un limite di un parametro di tipo richiede problemi, come dimostrato dalla complessità della risposta. In pratica stai chiedendo a ciascun lettore del tuo codice di leggere e comprendere quella risposta prima che possano capire il tuo codice. In altre parole: se il tuo programma si interrompe quando l'inferenza del tipo è migliorata, non sei in un territorio sicuro. In bocca al lupo!
Stephan Herrmann,

Risposte:


4

Le regole dell'inferenza del tipo hanno ricevuto una revisione significativa in Java 8, in particolare l'inferenza del tipo di destinazione è stata notevolmente migliorata. Quindi, mentre prima di Java 8 il sito dell'argomento del metodo non riceveva alcuna inferenza, impostando di default il tipo cancellato ( Class1per getClass1()e Interface1per getInterface1()), in Java 8 si deduce il tipo applicabile più specifico. JLS per Java 8 ha introdotto un nuovo capitolo, capitolo 18. Inference Type che manca in JLS per Java 7.


Il tipo applicabile più specifico per <T extends Interface1>è <X extends RequiredClass & BottomInterface>, dove RequiredClassè una classe richiesta da un contesto, ed BottomInterfaceè un tipo di fondo per tutte le interfacce (incluso Interface1).

Nota: ogni tipo di Java può essere rappresentato come SomeClass & SomeInterfaces. Poiché RequiredClassè un sottotipo di SomeClass, ed BottomInterfaceè un sottotipo di SomeInterfaces, Xè un sottotipo di ogni tipo Java. Pertanto, Xè un tipo di fondo Java.

Xpartite entrambi public static <T> void mandatory(T o)e public static <T extends Class2> void mandatory(T o)metodi di firme in quanto Xè Java tipo di fondale.

Quindi, secondo §15.12.2 , mandatory(getInterface1())chiama il sovraccarico più specifico del mandatory()metodo, che è public static <T extends Class2> void mandatory(T o)poiché <T extends Class2>è più specifico di <T>.

Ecco come è possibile specificare esplicitamente il getInterface1()parametro type per farlo restituire il risultato che corrisponde alla public static <T extends Class2> void mandatory(T o)firma del metodo:

public static <T extends Class2 & Interface1> void helper() {
    mandatory(Test.<T>getInterface1()); // prints "T is class2"
}

Il tipo applicabile più specifico per <T extends Class1>è <Y extends Class1 & BottomInterface>, dove BottomInterfaceè un tipo di fondo per tutte le interfacce.

Ycorrisponde alla public static <T> void mandatory(T o)firma del metodo, ma non corrisponde alla public static <T extends Class2> void mandatory(T o)firma del metodo poiché Ynon si estende Class2.

Quindi mandatory(getClass1())chiama il public static <T> void mandatory(T o)metodo.

Diversamente da getInterface1(), non è possibile specificare esplicitamente il getClass1()parametro type per farlo restituire il risultato che corrisponde alla public static <T extends Class2> void mandatory(T o)firma del metodo:

                       java: interface expected here
                                     
public static <T extends Class1 & C̲l̲a̲s̲s̲2> void helper() {
    mandatory(Test.<T>getClass1());
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.