【问题标题】:Java 8 ambiguous method reference for generic class泛型类的 Java 8 模糊方法参考
【发布时间】:2015-04-07 13:58:39
【问题描述】:

以下代码在 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

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

编辑:

只是回答一些 cmets,很明显,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

编辑2:

在 javac 1.8.0_40 下编译失败,同样的错误

【问题讨论】:

  • 即使使用具体的泛型,这也是个问题
  • 是的,但是根据JLS,应该选择更具体的方法
  • @xendoo 问题发生在运行时。然后,泛型类型被删除,无法确定差异/两种方法同样不具体。
  • 在计算机上测试您的代码后,我在使用 Java 8u31 的 Eclipse Luna 4.4 中没有收到任何警告或错误 - picture

标签: java generics java-8 jls


【解决方案1】:

JLS, chapter §15.12.2.5 Choosing the Most Specific Method 很难读,但包含一个有趣的摘要:

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

我们可以通过以下示例轻松证明您的情况:

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&lt;&gt;("") 的类型SecondGenericClass&lt;String&gt; 是从它的参数"" 推断出来的,就是这样。所以对于verifyThat(new SecondGenericClass&lt;&gt;(""), new GenericClass&lt;&gt;("")) 的调用,参数的类型为SecondGenericClass&lt;String&gt;GenericClass&lt;String&gt;,这使得&lt;T&gt; void verifyThat(T,GenericClass&lt;T&gt;) 方法不适用。

请注意,有一个模棱两可的调用示例在 Java 7(甚至 Java 6)下表现出模棱两可:verifyThat(null, null); 在使用 javac 时会引发编译器错误。

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

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

是明确模棱两可的(在 Java 8 中),甚至 Eclipse 也同意这一点。相比之下,调用

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

具体到足以使第二种方法不适用并调用第一种方法,这为我们提供了有关 Java 7 中发生了什么的提示,其中new GenericClass&lt;&gt;("") 的类型固定为GenericClass&lt;String&gt;,就像new GenericClass&lt;String&gt;("") 一样。


归根结底,从 Java 7 到 Java 8(显着)变化的不是选择最具体的方法,而是改进了类型推断带来的适用性。一旦两种方法都适用,调用就很模糊,因为两种方法都不比另一种更具体。

【讨论】:

    【解决方案2】:

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

    如果您将verifyThat 的第二个定义中的T 替换为SecondGenericClass,则签名匹配。

    换句话说,想象一下尝试像这样调用verifyThat 的第二个定义:

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

    在运行时,无法确定调用哪个版本的verifyThat,因为变量t 的类型是SecondGenericClass&lt;T&gt;T 的有效替换。

    请注意,如果 Java 已经具体化了泛型(有一天它会),在这个例子中,一个方法签名并不比另一个更具体。填补漏洞...

    【讨论】:

    • “为了检查适用性,调用参数的类型通常不能作为分析的输入。”
    • @xendo S &lt;: T 表示 proper subtypeSecondGeneric&lt;T&gt;Object 都是正确的超类型。 S &lt;1 T 用于直接子类型
    • 在删除泛型类型 T 后,我用类似函数的示例更新了我的问题,为它们生成的字节码与以前的函数相同。但是这次编译在 Java 7 和 8 中都成功了
    • @VinceEmigh 仍然是,SecondGeneric 是 Object 的正确子类型,应该用于选择更具体的方法
    • 这远不是一个有效的解释。在运行时,t 的类型并不重要,因为方法的选择发生在 编译时。抱怨类型擦除并没有真正的帮助。与具体类型没有任何区别。
    猜你喜欢
    • 2014-05-08
    • 1970-01-01
    • 2015-02-11
    • 2010-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多