问题 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<>(""));
    }

}

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

我已经回顾了以下所有变化:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2 
https://docs.oracle.com/javase/specs/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<>(""));
}

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

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

EDIT2: 

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


4249
2018-04-07 13:58


起源

即使使用具体化的仿制药,这也是一个问题 - jkschneider
可能重复: stackoverflow.com/questions/28466925/... - assylias
是的,但根据JLS,应该选择更具体的方法 - xendoo
@xendoo问题发生在运行时。然后,擦除泛型类型并且不可能确定差异/两种方法同样是特定的。 - SME_Dev
上了计算机并测试代码后,我在使用Java 8u31的Eclipse Luna 4.4中没有任何警告或错误 - 图片 - Vince Emigh


答案:


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));

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

在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有 调用适用性推断 (我们对JLS 7有一个区别,这是一个全新的章节...),它允许编译器选择使候选方法适用的类型参数(通过嵌套调用工作)。你可以为你的特殊情况找到这样的类型参数,你甚至可以找到一个适合两者的类型参数,

GenericTest.<Object>verifyThat(new SecondGenericClass<>(""), new GenericClass<>(""));

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

verifyThat(new SecondGenericClass<>(""), new GenericClass<String>(""));

具体到足以使第二个方法不适用并调用第一个方法,这给了我们一个关于Java 7中发生了什么类型的提示 new GenericClass<>("") 固定为 GenericClass<String> 就像 new GenericClass<String>("")


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


11
2018-04-07 17:11





在解决在多种方法适用的情况下使用哪种方法时, “......一般来说,调用参数的类型不能作为分析的输入。”  Java 7规范缺少此资格。

如果你替换 T 在第二个定义中 verifyThat 对于 SecondGenericClass 签名匹配。

换句话说,想象一下试图调用第二个定义 verifyThat 喜欢这个:

SecondGenericClass<String> t = new SecondGenericClass<String>("foo");
GenericTest.verifyThat(t, new GenericClass<String>("bar"));

在运行时,无法确定哪个版本 verifyThat 从变量的类型调用 t 是两者的有效替代 SecondGenericClass<T> 和 T

请注意,如果Java已经确定了泛型(并且它将在某一天),则在此示例中,一个方法签名并不比另一个更具体。堵塞漏洞......


1
2018-04-07 14:13



“为了检查适用性,调用参数的类型通常不能作为分析的输入。” - jkschneider
@xendo S <: T 指示 适当的亚型,两者兼而有之 SecondGeneric<T> 和 Object 是适当的超类型。 S <1 T 是直接的亚型 - Vince Emigh
在擦除泛型类型T后,我用类似函数的示例更新了我的问题,为它们生成的字节码与先前的函数相同。但这次编译在Java 7和8中都取得了成功 - xendoo
@VinceEmigh仍然是,SecondGeneric <T>是Object的正确子类型,应该用来选择更具体的方法 - xendoo
这远非有效的解释。在运行时,类型 t 无关紧要,因为方法的选择发生在 编译时间。关于类型擦除的咆哮并没有真正帮助;具体类型没有任何区别。 - Holger