Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

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.

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.

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.

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.

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top