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?

Hosam Aly
  • 41,555
  • 36
  • 141
  • 182

6 Answers6

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.

eljenso
  • 16,789
  • 6
  • 57
  • 63
  • 1
    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 Feb 01 '09 at 19:53
  • 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 Feb 02 '09 at 07:12
  • 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 Feb 02 '09 at 08:30
  • (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 Feb 02 '09 at 08:35
  • 1
    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 Feb 02 '09 at 15:12
  • I've updated the answer, I hope you like it better now. Thanks Scott. – eljenso Feb 02 '09 at 16:00
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.

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
  • In that case, wouldn't f(Object,boolean) be the more "specific" method? – Zach Scrivena Feb 01 '09 at 19:41
  • 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 Feb 01 '09 at 19:59
  • @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 Feb 01 '09 at 20:21
  • "because boxing and unboxing carry performance penalties." True, but not the issue here. Phase 1 is to guarantee backward compatibility with pre-JDK5. – eljenso Feb 01 '09 at 20:59
  • @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 Feb 02 '09 at 07:15
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.

Niyaz
  • 53,943
  • 55
  • 151
  • 182
  • 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 Feb 01 '09 at 19:43
  • If there are many more arguments in the given example, how does the compiler select the "most specific overload"? That is the problem. – Niyaz Feb 01 '09 at 19:45
  • 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 Feb 01 '09 at 19:48
  • 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 Feb 01 '09 at 19:51
  • 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 Feb 01 '09 at 19:58
  • 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 Feb 01 '09 at 20:03
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.

Kevin
  • 30,111
  • 9
  • 76
  • 83
  • 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 Feb 01 '09 at 19:41
  • 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 Feb 01 '09 at 19:49
  • 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 Feb 01 '09 at 19:56
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.

iny
  • 7,339
  • 3
  • 31
  • 36
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.

Agarax
  • 11
  • 1