为什么在使用不相关的接口类型调用时,编译器为什么选择带有类类型参数的通用方法?


11

考虑以下两个类和接口:

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

为什么第二个调用mandatory使用Class2,如果getInterface1Interface1没有关系来调用重载方法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中的方法重载带来了很多惊喜,仅应格外小心。如答案的复杂性所示,仅通过类型参数的界限来区分两个重载就很麻烦。您基本上是在要求代码的每个阅读者阅读并理解该答案,然后他们才能理解您的代码。换种说法:如果您的程序在类型推断得到改进时就中断了,那么您就不会处于安全地带。祝好运!
斯蒂芬·赫尔曼

Answers:


4

在Java 8中,类型推断的规则已经得到了重大改进,最显着的是目标类型推断得到了很大的改进。因此,尽管在Java 8之前,方法参数站点未收到任何推断,默认为擦除类型(Class1for getClass1()Interface1for getInterface1()),但在Java 8中,推断出最具体的适用类型。JLS for Java 8引入了新的章节第18章。JLS for Java 7中缺少类型推断


最具体的适用类型<T extends Interface1><X extends RequiredClass & BottomInterface>,其中RequiredClass是上下文所需的类,并且BottomInterface是所有接口(包括Interface1)的底部类型。

注意:每种Java类型都可以表示为SomeClass & SomeInterfaces。因为RequiredClass是的子类型SomeClass,并且BottomInterface是的子类型SomeInterfaces,所以X是每种Java类型的子类型。因此,X是Java底部类型。

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是所有接口的底部类型。

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)方法。

与with不同getInterface1(),您不能显式指定getClass1()type参数以使其返回与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());
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.