Why does autoboxing make some calls ambiguous in Java?
Asked Answered
A

6

35

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?

Afterbrain answered 1/2, 2009 at 19:20 Comment(0)
D
36

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.

Darees answered 1/2, 2009 at 19:47 Comment(6)
Thank you @eljenso. This clarifies the compiler's issue, but then it makes me wonder why the second phase is defined as so. Couldn't it be amended with "doing the least possible number of boxing/unboxing conversions"?Afterbrain
Well, thank you @eljenso! I agree that, in your example, the call is ambiguous. I would think that the two overloads will cost the same number of boxing conversions, so the call is indeed ambiguous. But (regardless of the JLS), I don't see my example ambiguous. What do you think?Afterbrain
I can understand your point of view when you say that you think f(Object, boolean) is a better match than f(Object, Object). However, the JLS is unambiguous here and says your call is ambiguous. You will have to come up with your own language to implement your proposed method lookup algorithm.Darees
(contd.) But you may not call it Java then. Method overloading is already one of the hardest parts to understand/support in a statically typed language. So instead of complicating things even further with boxing, it would be best to avoid overloading or disallow it altogether.Darees
Actually, the real reason for the error is §15.12.2.5. The rules for selecting the "most specific method" from the list only consider subtyping conversions, and don't take into the account auto boxing that are considered during "phase 2". You may want to update the answer.Saintjust
I've updated the answer, I hope you like it better now. Thanks Scott.Darees
R
5

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.

Rhaetian answered 1/2, 2009 at 19:38 Comment(5)
In that case, wouldn't f(Object,boolean) be the more "specific" method?Desrosiers
Thank you. But why is it not trying to perform the least possible number of boxing/unboxing conversions? Why is it all or none?Afterbrain
@Hosam: Excellent question, but I don't know the definitive answer. Possibly because it would be too computationally complex to find the invocation with the least conversions. It's a much simpler implementation for the compiler to apply boxing to all parameters or none.Rhaetian
"because boxing and unboxing carry performance penalties." True, but not the issue here. Phase 1 is to guarantee backward compatibility with pre-JDK5.Darees
@Bill, I don't think it's too complex computationally. It's just a matter of calculating the number of conversions for each candidate method, sorting them, and choosing the best one (unless the first two are equal). This does't seem complex to me!Afterbrain
S
3

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.

Sachsen answered 1/2, 2009 at 19:36 Comment(6)
Thanks @Niyaz. But even if a and b can be converted to objects, it's not necessary. So, the compiler (IMHO) must convert a to an object, but for b it should choose the most specific overload, which is the first one. What's wrong with this logic?Afterbrain
If there are many more arguments in the given example, how does the compiler select the "most specific overload"? That is the problem.Sachsen
Niyaz. what difference does it make if he casts to Object ? both functions accept Object. so i would say by instinct that it doesn't make any better match.Before
As I said, when the compiler tries to match by itself: if A can be converted to an object, so can be B. So either of the two functions are possible. When we cast A to Object manually, (even though in both functions A is an Object) the compiler does not need any more casts. It just matches.Sachsen
But couldn't it try to "perform the least possible number of boxing/unboxing conversions"? That would lead to the most specific overload, wouldn't it? (Of course, if the number of least possible conversions ties for two or more methods then the call is ambiguous.)Afterbrain
The compiler can select functions by matching the arguments. It usually does like that. But tat is done only when there is NO ambiguity. If there is one, since there is no way to know which function the programmer REALLY intended to call, the compiler gave an error.Sachsen
S
2

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.

Soukup answered 1/2, 2009 at 19:35 Comment(3)
Thanks @Kevin. Yes, I could explicitly box it, but I didn't. So why didn't it choose the most specific overload, which in this case is the first one?Afterbrain
Because the compiler didn't have enough information to make that decision for you. Both methods applied because boolean can be boxed to an ObjectSoukup
I don't think this answers the question. When multiple methods apply, the one that requires the least amount of widening conversion is automatically chosen. You get an ambiguity error only when multiple methods need the same number of "widenings". My guess is that boxing isn't counted as widening.Comma
N
2

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.

Negotiant answered 1/2, 2009 at 19:46 Comment(3)
so, it's like "once auto-boxed, we can go dirty with other arguments too" and "if no auto-box happened yet, we try not to autobox others too" ?Before
Thank you @iny. I apologize for being out of votes for today!Afterbrain
@litb, yes apparently so. But I wonder why they are not trying to "perform the least possible number of boxing/unboxing conversions".Afterbrain
C
1

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.

Cloistral answered 31/1, 2012 at 21:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.