我今天注意到自动装箱有时会导致方法重载分辨率的模糊。最简单的例子似乎是:
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); }
正确地按预期正确调用第一个重载。
那么为什么重载决策失败了呢?为什么编译器没有自动包装第一个参数,并正常接受第二个参数?为什么我必须明确要求自动装箱?
答案 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的子类型。第一次重载总是比第二次更具体(适用时)。