为什么自动装箱会使Java中的一些调用变得模棱两可?

我今天注意到,自动装箱有时会导致方法重载解析中的歧义。最简单的例子似乎是这样的:

public class Test {
    static void f(Object a, boolean b) {}
    static void f(Object a, Object b) {}

    static void m(int a, boolean b) { f(a,b); }
}

编译时,它会导致以下错误:

Test.java:5: reference to f is ambiguous, both method
    f(java.lang.Object,boolean) in Test and method
    f(java.lang.Object,java.lang.Object) in Test match

static void m(int a, boolean b) { f(a, b); }
                                  ^

此错误的修复是微不足道的:只需使用显式自动装箱:

static void m(int a, boolean b) { f((Object)a, b); }

它按预期正确调用第一个重载。

那么,为什么过载解决方案失败了呢?为什么编译器没有自动装箱第一个参数,而正常接受第二个参数?为什么我必须明确请求自动装箱?


答案 1

当您自己将第一个参数强制转换为 Object 时,编译器将在不使用自动装箱的情况下匹配该方法 (JLS3 15.12.2):

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

如果你没有显式地转换它,它将进入第二阶段,尝试找到一个匹配的方法,允许自动装箱,然后它确实是模棱两可的,因为你的第二个参数可以通过布尔值或Object匹配。

第二阶段 (§15.12.2.3) 在允许装箱和取消装箱的同时执行重载解析,但仍排除使用可变 arity 方法调用。

为什么在第二阶段,编译器不选择第二种方法,因为不需要对布尔参数进行自动装箱?因为在找到两种匹配的方法后,只有子类型转换用于确定两种方法中最具体的方法,而不管首先为匹配它们而进行的任何装箱或取消装箱(§15.12.2.5)。

另外:编译器不能总是根据所需的自动(取消)装箱数量选择最具体的方法。它仍然可能导致不明确的情况。例如,这仍然是模棱两可的:

public class Test {
    static void f(Object a, boolean b) {}
    static void f(int a, Object b) {}

    static void m(int a, boolean b) { f(a, b); } // ambiguous
}

请记住,用于选择匹配方法的算法(编译时步骤 2)是固定的,并在 JLS 中进行了描述。一旦进入阶段2,就没有选择性的自动装箱或取消装箱。编译器将找到所有可访问的方法(在这些情况下都是方法)和适用的方法(同样是两种方法),然后才选择最具体的方法,而不查看装箱/取消装箱,这在这里是模棱两可的。


答案 2

编译器确实自动框住了第一个参数。一旦完成,第二个参数就是模棱两可的,因为它可以被视为布尔值或对象。

本页介绍自动装箱的规则以及选择要调用的方法。编译器首先尝试在不使用任何自动装箱的情况下选择一种方法,因为装箱和取消装箱会降低性能。如果不诉诸装箱,则无法选择任何方法(如此例中所示),则该方法的所有参数都在表上。