views:

1209

answers:

5

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?

+2  A: 

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.

Kevin
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?
Hosam Aly
Because the compiler didn't have enough information to make that decision for you. *Both* methods applied because boolean can be boxed to an Object
Kevin
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.
erickson
+2  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.

Niyaz
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?
Hosam Aly
If there are many more arguments in the given example, how does the compiler select the "most specific overload"? That is the problem.
Niyaz
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.
Johannes Schaub - litb
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.
Niyaz
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.)
Hosam Aly
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.
Niyaz
+2  A: 

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.

Bill the Lizard
In that case, wouldn't f(Object,boolean) be the more "specific" method?
Zach Scrivena
Thanks @Bill. @Zach asked the same question I had in mind.
Hosam Aly
I edited my answer to cover that question.
Bill the Lizard
Thank you. But why is it not trying to perform the least possible number of boxing/unboxing conversions? Why is it all or none?
Hosam Aly
@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.
Bill the Lizard
"because boxing and unboxing carry performance penalties." True, but not the issue here. Phase 1 is to guarantee backward compatibility with pre-JDK5.
eljenso
@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!
Hosam Aly
+2  A: 

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.

iny
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" ?
Johannes Schaub - litb
Thank you @iny. I apologize for being out of votes for today!
Hosam Aly
@litb, yes apparently so. But I wonder why they are not trying to "perform the least possible number of boxing/unboxing conversions".
Hosam Aly
+14  A: 

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.

eljenso
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"?
Hosam Aly
Excellent answer!
Ray Hidayat
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?
Hosam Aly
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.
eljenso
(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.
eljenso
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.
Scott Wisniewski
I've updated the answer, I hope you like it better now. Thanks Scott.
eljenso