Чому компілятор вибирає цей загальний метод з параметром типу класу, коли викликається непов'язаним типом інтерфейсу?


11

Розглянемо наступні два класи та інтерфейс:

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

Чому другий виклик mandatoryвикликати перевантажений метод з Class2, якщо getInterface1і Interface1не має ніякого відношення 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;
    }
}

Я розумію, що Java 8 порушила сумісність з 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

І з Java 8 (також тестовано з 11 та 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
Підсумок: перевантаження методом на Java приносить стільки сюрпризів, і їх слід застосовувати лише з особливою обережністю. Дискримінація двох перевантажень лише зв'язаним параметром типу задає проблеми, про що свідчить складність відповіді. Ви в основному просите кожного читача свого коду прочитати та зрозуміти цю відповідь, перш ніж вони зможуть зрозуміти ваш код. По-іншому: якщо ваша програма перерветься, коли поліпшується умови вибору, ви не знаходитесь на безпечній території. Удачі!
Стефан Геррман

Відповіді:


4

Правила виведення типу отримали значну реконструкцію в Java 8, особливо помітно виведення цільового типу значно вдосконалено. Отже, тоді як до Java 8 сайт-аргумент методу не отримував жодного висновку, дефолтуючи до стираного типу ( Class1за getClass1()і Interface1для getInterface1()), у Java 8 виводиться найбільш конкретний застосований тип. JLS для Java 8 представив нову главу Глава 18. Введіть умовиводи, яких немає в JLS для Java 7.


Найбільш специфічним застосованим типом <T extends Interface1>є те <X extends RequiredClass & BottomInterface>, де RequiredClassце клас, необхідний контексту, і BottomInterfaceце нижній тип для всіх інтерфейсів (у тому числі Interface1).

Примітка. Кожен тип Java може бути представлений як SomeClass & SomeInterfaces. Оскільки RequiredClassпідтип SomeClassі BottomInterfaceє підтипом SomeInterfaces, Xє підтипом кожного типу Java. Тому Xце нижній тип Java.

Xвідповідає обом public static <T> void mandatory(T o)і public static <T extends Class2> void mandatory(T o)методам підписів, оскільки Xце нижній тип Java.

Так, за словами §15.12.2 , mandatory(getInterface1())викликає найбільш специфічні перевантажувати mandatory()метод, який з public static <T extends Class2> void mandatory(T o)тих пір <T extends Class2>є більш конкретним , ніж <T>.

Ось як можна чітко вказати getInterface1()параметр типу, щоб змусити його повернути результат, який відповідає public static <T extends Class2> void mandatory(T o)підпису методу:

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

Найбільш специфічним застосованим типом <T extends Class1>є те <Y extends Class1 & BottomInterface>, де BottomInterfaceце нижній тип для всіх інтерфейсів.

Yвідповідає public static <T> void mandatory(T o)сигнатурі методи, але це не відповідає public static <T extends Class2> void mandatory(T o)сигнатурі методи , оскільки Yне поширюється Class2.

Так mandatory(getClass1())називає public static <T> void mandatory(T o)метод.

На відміну від з getInterface1(), ви не можете явно вказати getClass1()параметр типу, щоб змусити його повернути результат, що відповідає public static <T extends Class2> void mandatory(T o)підпису методу:

                       java: interface expected here
                                     
public static <T extends Class1 & C̲l̲a̲s̲s̲2> void helper() {
    mandatory(Test.<T>getClass1());
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.