为什么autoboxing会在Java中使一些调用变得模棱两可?

时间:2009-02-01 19:20:48

标签: java compiler-construction overloading autoboxing

我今天注意到自动装箱有时会导致方法重载分辨率的模糊。最简单的例子似乎是:

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

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

那么为什么重载决策失败了呢?为什么编译器没有自动包装第一个参数,并正常接受第二个参数?为什么我必须明确要求自动装箱?

6 个答案:

答案 0 :(得分:33)

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

  

第一阶段(§15.12.2.2)执行   不允许的重载决议   装箱或拆箱转换,或者   使用变量arity方法   调用。如果没有适用的方法   在这个阶段找到了   处理继续到第二个   相。

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

  

第二阶段(§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,就没有选择性自动装箱或拆箱。编译器将找到所有可访问的方法(在这些情况下都是这两种方法)和适用的方法(同样是两种方法),然后只选择最具体的方法而不查看装箱/拆箱,这是这里很暧昧。

答案 1 :(得分:4)

编译器 自动装箱第一个参数。一旦完成,它就是第二个不明确的参数,因为它可以被看作是布尔值或对象。

This page解释了自动装箱的规则并选择了要调用的方法。编译器首先尝试选择方法而不使用任何自动装箱,因为装箱和拆箱会带来性能损失。如果没有诉诸拳击就无法选择任何方法,就像在这种情况下一样,那么装箱就会出现在该方法的所有参数的表格上。

答案 2 :(得分:3)

当你说 f(a,b )时,编译器会对它应该引用哪个函数感到困惑。

这是因为 a int ,但 f 中预期的参数是Object。因此,编译器决定将 a 转换为Object。现在的问题是,如果 a 可以转换为对象,那么可以 b

这意味着函数调用可以引用任一定义。这使得呼叫变得模棱两可。

当您手动将 a 转换为Object时,编译器只会查找最接近的匹配,然后引用它。

  

为什么编译器没有选择   “做”可以达到的功能   尽可能少的   拳击/拆箱转换“?

请参阅以下案例:

f(boolean a, Object b)
f(Object a , boolean b)

如果我们调用 f(布尔a,布尔b),它应该选择哪个函数?它很暧么吧?同样,当存在大量参数时,这将变得更加复杂。所以编译器选择给你一个警告。

由于无法知道程序员真正打算调用哪一个函数,编译器会给出错误。

答案 3 :(得分:2)

  

那么为什么重载决议   失败?为什么没有编译器自动框   第一个参数,并接受   第二个论点通常?我为什么   必须要求自动拳击   明确地

它通常不接受第二个参数。请记住,“boolean”也可以装入Object。您可以将boolean参数显式地转换为Object,并且它可以工作。

答案 4 :(得分:2)

请参阅http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#20448

强制转换有帮助,因为找不到要调用的方法就不需要装箱。没有强制转换,第二次尝试是允许装箱,然后布尔值也可以装箱。

最好有明确且易于理解的规范来说出会发生什么,而不是让人猜测。

答案 5 :(得分:1)

Java编译器分阶段解决重载的方法和构造函数。在第一阶段[§15.12.2.2]中,它通过子类型确定适用的方法[§4.10]。在此示例中,两种方法都不适用,因为int不是Object的子类型。

在第二阶段[§15.12.2.3]中,编译器通过方法调用转换[§5.3]识别适用的方法,这是自动装箱和子类型的组合。对于两个重载,int参数都可以转换为Integer,它是Object的子类型。 boolean参数不需要第一次重载的转换,并且可以转换为布尔值,第二种是Object的子类型。因此,这两种方法都适用于第二阶段。

由于可以使用多种方法,编译器必须确定哪种方法最具体[§15.12.2.5]。它比较参数类型,而不是参数类型,并且它不会自动进行它们。对象和布尔值是不相关的类型,因此它们被认为是同样具体的。这两种方法都没有比另一种方法更具体,因此方法调用是不明确的。

解决歧义的一种方法是将布尔参数更改为类型Boolean,它是Object的子类型。第一次重载总是比第二次更具体(适用时)。