Pourquoi le compilateur choisit-il cette méthode générique avec un paramètre de type de classe lorsqu'il est appelé avec un type d'interface sans rapport?

11

Considérez les deux classes et l'interface suivantes:

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

Pourquoi le deuxième appel à mandatoryappeler la méthode surchargée avec Class2, si getInterface1et Interface1n'a aucune relation avec 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;
    }
}

Je comprends que Java 8 a rompu la compatibilité avec 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

Et avec Java 8 (également testé avec 11 et 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
froque
la source
1
Conclusion: la surcharge de méthode en Java apporte tellement de surprises, elle ne doit être utilisée qu'avec un soin extrême. Discriminer deux surcharges uniquement par une limite d'un paramètre de type pose problème, comme le montre la complexité de la réponse. Vous demandez essentiellement à chaque lecteur de votre code de lire et de comprendre cette réponse avant de pouvoir comprendre votre code. Autrement dit: si votre programme s'arrête lorsque l'inférence de type est améliorée, vous n'êtes pas sur un territoire sûr. Bonne chance!
Stephan Herrmann

Réponses:

4

Les règles d'inférence de type ont fait l'objet d'une refonte importante dans Java 8, notamment l'inférence de type cible a été considérablement améliorée. Ainsi, alors qu'avant Java 8, le site d'arguments de méthode ne recevait aucune inférence, par défaut sur le type effacé ( Class1pour getClass1()et Interface1pour getInterface1()), dans Java 8, le type applicable le plus spécifique est déduit. Le JLS pour Java 8 a introduit un nouveau chapitre Chapitre 18. Type Inférence qui manque dans JLS pour Java 7.


Le type applicable le plus spécifique pour <T extends Interface1>est <X extends RequiredClass & BottomInterface>, où RequiredClassest une classe requise par un contexte, et BottomInterfaceest un type inférieur pour toutes les interfaces (y compris Interface1).

Remarque: Chaque type Java peut être représenté par SomeClass & SomeInterfaces. Puisque RequiredClassest un sous-type de SomeClass, et BottomInterfaceest un sous-type de SomeInterfaces, Xest un sous-type de chaque type Java. Par conséquent, il Xs'agit d'un type inférieur Java.

Xcorrespond à la fois aux signatures de méthodes public static <T> void mandatory(T o)et est du type Java bas.public static <T extends Class2> void mandatory(T o)X

Ainsi, selon le §15.12.2 , mandatory(getInterface1())appelle la surcharge de mandatory()méthode la plus spécifique , qui est public static <T extends Class2> void mandatory(T o)depuis <T extends Class2>plus spécifique que <T>.

Voici comment vous pouvez spécifier explicitement le getInterface1()paramètre type pour qu'il renvoie le résultat qui correspond à la public static <T extends Class2> void mandatory(T o)signature de la méthode:

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

Le type applicable le plus spécifique pour <T extends Class1>est <Y extends Class1 & BottomInterface>, où BottomInterfaceest un type inférieur pour toutes les interfaces.

Y allumettes public static <T> void mandatory(T o) signature de la méthode, mais il ne correspond pas à la public static <T extends Class2> void mandatory(T o)signature de la méthode car Yil ne s'étend pas Class2.

Donc mandatory(getClass1())appelle public static <T> void mandatory(T o)méthode.

Contrairement à with getInterface1(), vous ne pouvez pas spécifier explicitement le getClass1()paramètre type pour lui faire retourner le résultat qui correspond à la public static <T extends Class2> void mandatory(T o)signature de la méthode:

                       java: interface expected here
                                     
public static <T extends Class1 & C̲l̲a̲s̲s̲2> void helper() {
    mandatory(Test.<T>getClass1());
}
Bananon
la source