【问题标题】:Why do two methods with signature (primitive, wrapper) and (primitive, primitive) cause the method call (wrapper, primitive) to be ambiguous?为什么带有签名(primitive,wrapper)和(primitive,primitive)的两个方法会导致方法调用(wrapper,primitive)不明确?
【发布时间】:2014-03-18 05:41:35
【问题描述】:

这只是一个练习,但我无法弄清楚其中的歧义:

private static void flipFlop(String str, int i, Integer iRef) {
System.out.println(str + "ciao");
}

private static void flipFlop(String str, int i, int j) {
System.out.println(str + "hello");
}

public static void main(String[] args) {
flipFlop("hello", new Integer(4), 2004);
}

上面写着:

方法 flipFlop(String, int, Integer) 对于类型不明确 测试

我猜想第二个参数会被解包为 int,所以第二个 flipFlop 方法会是选择。

【问题讨论】:

  • 最后一个参数可以装箱到Integer 或作为原语应用
  • @Kayaman:链接它。我们都知道它可能在 JLS 的某个地方,只是说它在那里并没有增加任何价值。
  • 作为记录,它可以使用 Java 8 编译并打印“hellohello”。
  • @assylias 如果您能在此处找到并添加明确的 Java 8 JLS 参考,那就太好了。
  • @MiserableVariable:这也是我的假设,是的。他们可能将其添加到第 2 阶段(如我的回答中所述)。

标签: java jdk1.6


【解决方案1】:

如果您喜欢引人入胜的阅读,here is the relevant portion of the Java Language specification 描述了如何解决方法。

但基本上你的第三个参数可以被解释为一个原始的或一个自动装箱的包装器,编译器无法弄清楚你想要什么。这两种方法都是“最大限度地具体”使用 JLS 术语。

【讨论】:

  • 两者都具有最大特异性的原因是int <: IntegerInteger <: int 都不是(即两者都不是另一个的子类型)。
  • uhm...好吧,问题是我假设由于 int 参数可用,因此在需要尝试对原语进行自动装箱之前会选择第二种方法。
  • 因为你没有flipFlop(String, Integer, Integer)方法。
  • @user2336315:前者有一个完全匹配(第一种方法),而后者必须在intInteger之间做出最后一个参数的决定:模糊。
  • @JeroenVannevel 有道理。谢谢!但是编译器还不够聪明,无法进行最接近的匹配吗? (答案是否定的,否则我想我们没有这个错误)也就是说,在这种情况下也是第一种方法。
【解决方案2】:

好的,我已经仔细研究了 JLS,我相信这应该可以消除您可能存在的任何疑虑。

这是原来的问题:

public class Main {
    private static void flipFlop(int i, Integer iRef) {
        System.out.println("Method 1");
    }

    private static void flipFlop(int i, int j) {
        System.out.println("Method 2");
    }

    public static void main(String[] args) {
        flipFlop(new Integer(4), 2004);
    }
}

正如另一个答案中所指出的:这会失败,因为编译器无法决定使用什么重载。

但是您可能认为这没有任何意义。在这种情况下,编译器可以很好地决定他应该使用什么方法:

public class Main {
    private static void flipFlop(Integer y) {
        System.out.println("ciao");
    }

    private static void flipFlop(int j) {
        System.out.println("hello");
    }

    public static void main(String[] args) {
        flipFlop(new Integer(6));
        flipFlop(6);
    }
}

理性告诉我们,当你有 X + Y 和两个分别采用 Y + XY + Y 的方法时,你知道 XY是可互换的,那么这意味着后一种方法更具体。

JLS 中描述了这两者之间的区别。我在下面提供了整个工作流程,但重要的是:

首先,编译器会在禁止装箱/拆箱时查看具有相同签名的方法。在我们的第二个示例中,这不会导致任何问题,但在我们的第一个示例中,这不会返回一个可满足的方法,因为它们都没有将Integer 作为第一个参数..

如果失败,编译器会进入第二步,允许装箱/拆箱。这应该可以解决我们使用第一个参数时遇到的问题,但现在会导致第二个参数出现歧义,因为现在不确定您是指使用 int 还是使用 Integer 的重载。

这最终会导致模棱两可的方法调用。

15.12.2. Compile-Time Step 2: Determine Method Signature

  1. 第一阶段 (§15.12.2.2) 执行重载解析不允许装箱或拆箱转换,或使用可变的 arity 方法调用。如果在此阶段没有找到适用的方法,则处理继续到第二阶段。

  2. 第二阶段 (§15.12.2.3) 执行重载解析同时允许装箱和拆箱,但仍排除使用可变参数方法调用。如果在此阶段没有找到适用的方法,则处理继续到第三阶段。

如果在适用性测试的三个阶段之一中确定了几种适用的方法,则选择最具体的一种,如§15.12.2.5 部分所述。

15.12.2.5. Choosing the Most Specific Method

如果一个方法是可访问和适用的,并且没有其他严格更具体的适用和可访问的方法,则该方法被称为最大程度地特定于方法调用。

可能没有一种方法是最具体的,因为有两种或多种方法是最具体的。在这种情况下:

  • 如果所有最具体的方法都有覆盖等效 (§8.4.2) 签名,那么:(...一些规则来决定谁被选中...)

  • 否则,我们说方法调用有歧义,出现编译时错误。

【讨论】:

  • 所以基本上预装箱评估已应用于签名级别,而不是逐个参数,这就是我的推理不正确的原因。
  • @dierre:在第一阶段,它禁止了导致第一个参数歧义的拳击。在第二阶段,它允许装箱,这会导致与第二个参数的歧义(因为当允许装箱时,intInteger 是相等的)。差不多就是这样。
  • 我没有测试过,但是如果你编码flipFlop(new Integer(4), (int)2004)),错误会消失吗?
  • @Bohemian:没有。这是有道理的,因为拳击在第二阶段是允许的,而在第一阶段它不会找到合适的方法。到那时,它会找到 2 个可能的候选人,并且会尽我所能尝试所有的可能性。
【解决方案3】:

您的第二个参数2004int 也适用于Integer,因为auto-boxing,这就是编译器无法决定使用哪种方法的原因。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-21
    • 1970-01-01
    相关资源
    最近更新 更多