Warum wählt der Compiler diese generische Methode mit einem Klassentypparameter, wenn er mit einem nicht verwandten Schnittstellentyp aufgerufen wird?

11

Betrachten Sie die folgenden zwei Klassen und Schnittstellen:

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

Warum ruft der zweite Aufruf mandatorydie überladene Methode auf Class2, wenn getInterface1und Interface1hat keine Beziehung zu 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;
    }
}

Ich verstehe, dass Java 8 die Kompatibilität mit Java 7 gebrochen hat :

$ /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

Und mit Java 8 (auch mit 11 und 13 getestet):

$ /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
quelle
1
Fazit: Das Überladen von Methoden in Java bringt so viele Überraschungen mit sich, dass es nur mit äußerster Sorgfalt verwendet werden sollte. Das Unterscheiden von zwei Überladungen nur durch eine Grenze eines Typparameters führt zu Problemen, wie die Komplexität der Antwort zeigt. Grundsätzlich bitten Sie jeden Leser Ihres Codes, diese Antwort zu lesen und zu verstehen, bevor er Ihren Code verstehen kann. Anders ausgedrückt: Wenn Ihr Programm bei einer verbesserten Typinferenz unterbrochen wird, befinden Sie sich nicht auf sicherem Gebiet. Viel Glück!
Stephan Herrmann

Antworten:

4

Die Regeln für die Typinferenz wurden in Java 8 grundlegend überarbeitet. Insbesondere die Zieltypinferenz wurde erheblich verbessert. Während vor Java 8 die Methodenargument-Site keine Inferenz erhielt, standardmäßig der gelöschte Typ ( Class1für getClass1()und Interface1für getInterface1()), wird in Java 8 der spezifischste anwendbare Typ abgeleitet. Mit JLS für Java 8 wurde ein neues Kapitel Kapitel 18 eingeführt. Typinferenz , die in JLS für Java 7 fehlt.


Der spezifischste anwendbare Typ für <T extends Interface1>ist <X extends RequiredClass & BottomInterface>, wo RequiredClasseine Klasse für einen Kontext erforderlich ist, und BottomInterfaceist ein unterer Typ für alle Schnittstellen (einschließlich Interface1).

Hinweis: Jeder Java-Typ kann als dargestellt werden SomeClass & SomeInterfaces. Da RequiredClasses sich um einen Untertyp von SomeClassund einen BottomInterfaceUntertyp von handelt SomeInterfaces, Xhandelt es sich um einen Untertyp jedes Java-Typs. Daher Xhandelt es sich um einen Java-Bodentyp.

XEntspricht beiden public static <T> void mandatory(T o)und public static <T extends Class2> void mandatory(T o)Methodensignaturen, da Xes sich um den unteren Java-Typ handelt.

Also, nach §15.12.2 , mandatory(getInterface1())ruft die spezifischste Überlastung der mandatory()Methode, die ist public static <T extends Class2> void mandatory(T o)seit <T extends Class2>spezifischer als ist <T>.

So können Sie den getInterface1()Typparameter explizit angeben , damit das Ergebnis zurückgegeben wird, das der public static <T extends Class2> void mandatory(T o)Methodensignatur entspricht:

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

Der spezifischste anwendbare Typ für <T extends Class1>ist <Y extends Class1 & BottomInterface>, wo BottomInterfaceein unterer Typ für alle Schnittstellen ist.

YEntspricht der public static <T> void mandatory(T o)Methodensignatur, stimmt jedoch nicht mit der public static <T extends Class2> void mandatory(T o)Methodensignatur überein , da Ysie nicht erweitert wird Class2.

Also mandatory(getClass1())ruft public static <T> void mandatory(T o)Methode auf.

Im Gegensatz zu with getInterface1()können Sie den getClass1()Typparameter nicht explizit angeben , damit das Ergebnis zurückgegeben wird, das der public static <T extends Class2> void mandatory(T o)Methodensignatur entspricht:

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