为什么自动装箱会使 Java 中的某些调用变得不明确?

发布于 2024-07-12 13:10:05 字数 722 浏览 6 评论 0原文

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

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

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

那么为什么重载解析会失败呢? 为什么编译器不自动装箱第一个参数并正常接受第二个参数? 为什么我必须明确请求自动装箱?

I noticed today that auto-boxing can sometimes cause ambiguity in method overload resolution. The simplest example appears to be this:

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

When compiled, it causes the following error:

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

The fix to this error is trivial: just use explicit auto-boxing:

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

Which correctly calls the first overload as expected.

So why did the overload resolution fail? Why didn't the compiler auto-box the first argument, and accept the second argument normally? Why did I have to request auto-boxing explicitly?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

独孤求败 2024-07-19 13:10:05

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

第一阶段(§15.12.2.2)执行
未经允许的重载解析
装箱或拆箱转换,或者
使用可变数量方法
调用。 如果没有适用的方法
然后在这个阶段发现
处理继续到第二个
阶段。

如果你不显式地强制转换它,它会进入第二阶段尝试寻找匹配方法,允许自动装箱,然后它确实是模棱两可的,因为你的第二个参数可以通过布尔值或对象来匹配。

第二阶段(§15.12.2.3)执行
重载解析同时允许
装箱和拆箱,但仍然
排除使用变量数量
方法调用。

为什么在第二阶段编译器不选择第二种方法,因为不需要对布尔参数进行自动装箱? 因为在找到两个匹配方法之后,仅使用子类型转换来确定这两个方法中最具体的方法,而不管首先发生的任何装箱或拆箱以匹配它们(第 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 阶段,就不再有选择性的自动装箱或拆箱。 编译器将找到所有可访问的方法(在这些情况下都是方法)和适用的方法(同样是两个方法),然后才选择最具体的一个,而不考虑装箱/拆箱,即这里含糊不清。

When you cast the first argument to Object yourself, the compiler will match the method without using autoboxing (JLS3 15.12.2):

The first phase (§15.12.2.2) performs
overload resolution without permitting
boxing or unboxing conversion, or the
use of variable arity method
invocation. If no applicable method is
found during this phase then
processing continues to the second
phase.

If you don't cast it explicitly, it will go to the second phase of trying to find a matching method, allowing autoboxing, and then it is indeed ambiguous, because your second argument can be matched by boolean or Object.

The second phase (§15.12.2.3) performs
overload resolution while allowing
boxing and unboxing, but still
precludes the use of variable arity
method invocation.

Why, in the second phase, doesn't the compiler choose the second method because no autoboxing of the boolean argument is necessary? Because after it has found the two matching methods, only subtype conversion is used to determine the most specific method of the two, regardless of any boxing or unboxing that took place to match them in the first place (§15.12.2.5).

Also: the compiler can't always choose the most specific method based on the number of auto(un)boxing needed. It can still result in ambiguous cases. For example, this is still ambiguous:

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
}

Remember that the algorithm for choosing a matching method (compile-time step 2) is fixed and described in the JLS. Once in phase 2 there is no selective autoboxing or unboxing. The compiler will locate all the methods that are accessible (both methods in these cases) and applicable (again the two methods), and only then chooses the most specific one without looking at boxing/unboxing, which is ambiguous here.

黎夕旧梦 2024-07-19 13:10:05

编译器确实自动装箱了第一个参数。 完成后,第二个参数就变得不明确了,因为它可以被视为布尔值或对象。

此页面解释了自动装箱和选择要调用的方法的规则。 编译器首先尝试选择一个方法,根本不使用任何自动装箱,因为装箱和拆箱会带来性能损失。 如果不借助装箱就无法选择任何方法(如本例所示),则该方法的所有参数都可以进行装箱。

The compiler did auto-box the first argument. Once that was done, it's the second argument that's ambiguous, as it could be seen as either boolean or Object.

This page explains the rules for autoboxing and selecting which method to invoke. The compiler first tries to select a method without using any autoboxing at all, because boxing and unboxing carry performance penalties. If no method can be selected without resorting to boxing, as in this case, then boxing is on the table for all arguments to that method.

花落人断肠 2024-07-19 13:10:05

当您说f(a, b)时,编译器会混淆它应该引用哪个函数。

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

这意味着函数调用可以引用任一定义。 这使得调用变得不明确。

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

为什么编译器没有选择
可以通过“doing”来实现的功能
尽可能少的数量
装箱/拆箱转换”?

请看下面的情况:

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

如果我们像 f(boolean a, boolean b) 那样调用,它应该选择哪个函数?这很模糊吧?同样,当 a 时,这会变得更加复杂因此编译器选择向您发出警告,

因为无法知道程序员真正想要调用哪一个函数,因此编译器会给出错误。

When you say f(a, b), the compiler is confused as to which function it should reference to.

This is because a is an int, but the argument expected in f is an Object. So the compliler decides to convert a to an Object. Now the problem is that, if a can be converted to an object, so can be b.

This means that the function call can reference to either definitions. This makes the call ambiguous.

When you convert a to an Object manually, the compiler just looks for the closest match and then refers to it.

Why didn't the compiler select the
function that can be reached by "doing
the least possible number of
boxing/unboxing conversions"?

See the following case:

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

If we call like f(boolean a, boolean b), which function should it select? It ambigous right? Similarly, this will become more complex when a lot of arguments are present. So the compiler chose to give you a warning instead.

Since there is no way to know which one of the functions the programmer really intended to call, the compiler gives an error.

海拔太高太耀眼 2024-07-19 13:10:05

那么为什么要进行重载解析呢?
失败? 为什么编译器没有自动装箱
第一个参数,并接受
第二个参数通常? 为什么我
必须请求自动装箱
明确吗?

它通常不接受第二个参数。 请记住,“布尔值”也可以装箱到对象。 您也可以将布尔参数显式转换为 Object,并且它会起作用。

So why did the overload resolution
fail? Why didn't the compiler auto-box
the first argument, and accept the
second argument normally? Why did I
have to request auto-boxing
explicitly?

It didn't accept the second argument normally. Remember that "boolean" can be boxed to an Object too. You could have explicitly cast the boolean argument to Object as well and it would have worked.

白龙吟 2024-07-19 13:10:05

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

强制转换很有帮助,因为这样就不需要装箱来查找要调用的方法。 如果没有强制转换,第二次尝试是允许装箱,然后也可以对布尔值进行装箱。

最好有清晰易懂的规范来说明将会发生什么,而不是让人们猜测。

See http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#20448

The cast helps because then no boxing is needed to find the method to call. Without the cast the second try is to allow boxing and then also the boolean can be boxed.

It is better to have clear and understandable specs to say what will happen than to make people guess.

画离情绘悲伤 2024-07-19 13:10:05

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

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

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

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

The Java compiler resolves overloaded methods and constructors in phases. In the first phase [§15.12.2.2], it identifies applicable methods by subtyping [§4.10]. In this example, neither method is applicable, because int is not a subtype of Object.

In the second phase [§15.12.2.3], the compiler identifies applicable methods by method invocation conversion [§5.3], which is a combination of autoboxing and subtyping. The int argument can be converted to an Integer, which is a subtype of Object, for both overloads. The boolean argument needs no conversion for the first overload, and can be converted to Boolean, a subtype of Object, for the second. Therefore, both methods are applicable in the second phase.

Since more than one method is applicable, the compiler must determine which is most specific [§15.12.2.5]. It compares the parameter types, not the argument types, and it doesn't autobox them. Object and boolean are unrelated types, so they are considered equally specific. Neither method is more specific than the other, so the method call is ambiguous.

One way to resolve the ambiguity would be to change the boolean parameter to type Boolean, which is a subtype of Object. The first overload would always be more specific (when applicable) than the second.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文