Dlaczego kompilator wybiera tę ogólną metodę z parametrem typu klasy, gdy jest wywoływany z niepowiązanym typem interfejsu?

11

Rozważ następujące dwie klasy i interfejs:

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

Dlaczego drugie wywołanie mandatorywywołuje przeciążoną metodę Class2, jeśli getInterface1i Interface1nie ma związku z 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;
    }
}

Rozumiem, że Java 8 złamała kompatybilność z Javą 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

I z Javą 8 (testowany również z 11 i 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
fokusowy
źródło
1
Konkluzja: przeciążenie metody w Javie przynosi tyle niespodzianek, że należy jej używać z najwyższą ostrożnością. Rozróżnienie dwóch przeciążeń tylko przez granicę parametru typu wymaga kłopotów, o czym świadczy złożoność odpowiedzi. Zasadniczo prosi się każdego czytelnika kodu o przeczytanie i zrozumienie tej odpowiedzi, zanim będą oni mogli zrozumieć Twój kod. Mówiąc inaczej: jeśli Twój program ulegnie awarii, gdy poprawione zostanie wnioskowanie o typie, nie będziesz na bezpiecznym terytorium. Powodzenia!
Stephan Herrmann

Odpowiedzi:

4

Reguły wnioskowania o typach zostały znacznie zmienione w Javie 8, przede wszystkim znacznie poprawiono wnioskowanie o typach docelowych. Tak więc, podczas gdy przed Java 8 witryna argumentów metody nie otrzymała żadnego wnioskowania, domyślnie jest to typ wymazany ( Class1for getClass1()i Interface1for getInterface1()), w Javie 8 wywodzi się najbardziej odpowiedni typ. JLS dla Java 8 wprowadził nowy rozdział Rozdział 18. Wnioskowanie typu, którego brakuje w JLS dla Java 7.


Najbardziej konkretnym typem stosowanym dla <T extends Interface1>jest <X extends RequiredClass & BottomInterface>, gdzie RequiredClassjest klasa wymagana przez kontekst, i BottomInterfacejest typem dolnym dla wszystkich interfejsów (w tym Interface1).

Uwaga: Każdy typ Java może być reprezentowany jako SomeClass & SomeInterfaces. Ponieważ RequiredClassjest podtypem SomeClassi BottomInterfacejest podtypem SomeInterfaces, Xjest podtypem każdego typu Java. Dlatego Xjest typem Java na dole.

Xdopasowuje obie sygnatury public static <T> void mandatory(T o)i public static <T extends Class2> void mandatory(T o)metody, ponieważ Xjest to dolny typ Java.

Tak więc, zgodnie z §15.12.2 , mandatory(getInterface1())wywołuje najbardziej specyficzne przeciążenie mandatory()metody, które jest public static <T extends Class2> void mandatory(T o)odtąd <T extends Class2>bardziej szczegółowe niż <T>.

Oto jak możesz jawnie określić getInterface1()parametr type, aby zwracał wynik zgodny z public static <T extends Class2> void mandatory(T o)podpisem metody:

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

Najbardziej konkretnym typem stosowanym dla <T extends Class1>jest <Y extends Class1 & BottomInterface>, gdzie BottomInterfacejest typ dolny dla wszystkich interfejsów.

Ydopasowuje public static <T> void mandatory(T o)podpis metody, ale nie pasuje do public static <T extends Class2> void mandatory(T o)podpisu metody, ponieważ Ynie jest rozszerzany Class2.

Tak mandatory(getClass1())wywołuje public static <T> void mandatory(T o)metodę.

W przeciwieństwie do getInterface1(), nie można jawnie określić getClass1()parametru type, aby zwracał wynik zgodny z public static <T extends Class2> void mandatory(T o)podpisem metody:

                       java: interface expected here
                                     
public static <T extends Class1 & C̲l̲a̲s̲s̲2> void helper() {
    mandatory(Test.<T>getClass1());
}
Bananon
źródło