コンパイラは、無関係なインターフェイスタイプで呼び出されたときに、クラスタイプパラメータを持つこのジェネリックメソッドを選択するのはなぜですか?


11

次の2つのクラスとインターフェースを検討してください。

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

mandatoryオーバーロードされたメソッドを呼び出すための2番目の呼び出しが、との関係がないClass2場合getInterface1、なぜですか?Interface1Class2

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でのメソッドのオーバーロードは多くの驚きをもたらすため、細心の注意を払ってのみ使用する必要があります。答えが複雑であることからわかるように、2つのオーバーロードを型パラメーターの境界のみで区別すると、問題が発生します。基本的には、コードの各リーダーに、コードを理解する前にその回答を読んで理解するように依頼します。別の言い方をすると、型推論が改善されたときにプログラムが壊れた場合、安全な領域にいるわけではありません。幸運を!
Stephan Herrmann、

回答:


4

型推論のルールがJava 8で大幅に見直されました。特にターゲット型推論は大幅に改善されました。したがって、Java 8より前は、メソッド引数サイトは推論を受け取らず、デフォルトで消去されたタイプ(Class1for getClass1()およびInterface1for getInterface1())になりましたが、Java 8では、最も具体的な適用可能なタイプが推論されます。JLS for Java 8では、第18章の新しい章が導入されました。JLSfor Java 7にはない型推論


の最も具体的な適用可能なタイプ<T extends Interface1><X extends RequiredClass & BottomInterface>であり、RequiredClassはコンテキストに必要なクラスであり、BottomInterfaceすべてのインターフェース(を含むInterface1)の最下部のタイプです。

注:各Javaタイプはとして表すことができますSomeClass & SomeInterfaces。はのRequiredClassサブタイプでありSomeClass、のBottomInterfaceサブタイプであるためSomeInterfacesXすべてのJava型のサブタイプです。したがって、XJavaボトムタイプです。

XはJavaの最下位タイプであるため、public static <T> void mandatory(T o)およびの両方のpublic static <T extends Class2> void mandatory(T o)シグニチャに一致しXます。

だから、による§15.12.2mandatory(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はすべてのインターフェースの最下部のタイプです。

Ypublic 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());
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.