Java 8模糊方法参考泛型类

xen*_*doo 11 java generics jls java-8

下面的代码在Java 7中编译并运行正常,但无法在Java 1.8.0 u25中编译:

public class GenericTest {

    public static class GenericClass<T> {
        T value;

        public GenericClass(T value) {
            this.value = value;
        }
    }

    public static class SecondGenericClass<T> {
        T value;

        public SecondGenericClass(T value) {
            this.value = value;
        }
    }


    public static<T >void verifyThat(SecondGenericClass<T> actual, GenericClass<T> matcher) {
    }

    public static<T >void verifyThat(T actual, GenericClass<T> matcher) {
    }

    @Test
    public void testName() throws Exception {
        verifyThat(new SecondGenericClass<>(""), new GenericClass<>(""));
    }

}
Run Code Online (Sandbox Code Playgroud)

Java 8中的错误消息如下所示:

Error:(33, 9) java: reference to verifyThat is ambiguous
  both method <T>verifyThat(com.sabre.ssse.core.dsl.GenericTest.SecondGenericClass<T>,com.sabre.ssse.core.dsl.GenericTest.GenericClass<T>) in com.sabre.ssse.core.dsl.GenericTest and method <T>verifyThat(T,com.sabre.ssse.core.dsl.GenericTest.GenericClass<T>) in com.sabre.ssse.core.dsl.GenericTest match
Run Code Online (Sandbox Code Playgroud)

我已经回顾了以下所有变化:
https ://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2
https://docs.oracle.com/ JavaSE的/规格/ JLS/SE7/HTML/JLS-15.html#JLS-15.12.2

但我没有注意到这种行为的确切原因.

编辑:

只是回答一些评论,很明显Java 7和8中的编译器都能够处理这样的调用(签名类似于编译时类型擦除后留下的签名:

public static void verifyThat(SecondGenericClass actual, GenericClass matcher) {
}

public static void verifyThat(Object actual, GenericClass matcher) {
}

@Test
public void testName() throws Exception {
    verifyThat(new SecondGenericClass<>(""), new GenericClass<>(""));
}
Run Code Online (Sandbox Code Playgroud)

为两个泛型方法生成的字节码和擦除的字节码是相同的,如下所示:

public static verifyThat(Lcom/sabre/ssse/core/dsl/GenericTest$SecondGenericClass;Lcom/sabre/ssse/core/dsl/GenericTest$GenericClass;)V
public static verifyThat(Ljava/lang/Object;Lcom/sabre/ssse/core/dsl/GenericTest$GenericClass;)V
Run Code Online (Sandbox Code Playgroud)

EDIT2:

javac 1.8.0_40下的编译失败并出现相同的错误

Hol*_*ger 11

JLS,章节§15.12.2.5选择最具体的方法是一个难以阅读但包含一个有趣的总结:

非正式的直觉是,如果第一个方法处理的任何调用都可以传递给另一个没有编译时类型错误的调用,那么一个方法比另一个方法更具体.

我们可以通过以下示例轻松地反驳您的情况:

GenericTest.<String>verifyThat( // invokes the first method
    new SecondGenericClass<>(""), new GenericClass<>(""));
GenericTest.<SecondGenericClass<String>>verifyThat( // invokes the second
    new SecondGenericClass<>(""), new GenericClass<>(null));
Run Code Online (Sandbox Code Playgroud)

所以这里没有最具体的方法,但是,如示例所示,可以使用使其他方法不适用的参数调用任一方法.

在Java 7中,由于(编译器)尝试查找类型参数以使更多方法适用(也称为有限类型推断)的尝试有限,因此更容易使方法不适用.表达式new SecondGenericClass<>("")具有SecondGenericClass<String>从其参数推断出的类型"",就是这样.因此,对于调用verifyThat(new SecondGenericClass<>(""), new GenericClass<>("")),参数具有类型SecondGenericClass<String>,GenericClass<String>并且使该方法<T> void verifyThat(T,GenericClass<T>)不适用.

请注意,有一个模糊调用的例子表明Java 7(甚至Java 6)下的歧义:使用时verifyThat(null, null);会引发编译器错误javac.

但Java 8具有Invocation Applicability Inference(我们对JLS 7有一个不同之处,这是一个全新的章节......),它允许编译器选择使候选方法适用的类型参数(通过嵌套调用工作).你可以为你的特殊情况找到这样的类型参数,你甚至可以找到一个适合两者的类型参数,

GenericTest.<Object>verifyThat(new SecondGenericClass<>(""), new GenericClass<>(""));
Run Code Online (Sandbox Code Playgroud)

毫不含糊(在Java 8中),即使Eclipse也同意这一点.相比之下,调用

verifyThat(new SecondGenericClass<>(""), new GenericClass<String>(""));
Run Code Online (Sandbox Code Playgroud)

是不够具体呈现第二种方法不适用,并调用第一种方法,这给了我们一个关于Java 7中发生了什么暗示这里的类型new GenericClass<>("")固定为GenericClass<String>只是喜欢new GenericClass<String>("").


最重要的是,它不是选择从Java 7变为Java 8(显着)的最具体方法,而是由于改进的类型推断而适用性.一旦两种方法都适用,调用就不明确,因为这两种方法都不比另一种方法更具体.