Why is this method overloading ambiguous?
Asked Answered
D

1

23
public class Primitive {
    void m(Number b, Number ... a) {} // widening, autoboxing->widening->varargs

    void m(byte b, Number ... a) {} // unboxing, autoboxing->widening->varargs

    public static void main(String[] args) {
        Byte b = 12;
        Primitive obj = new Primitive();
        obj.m(b, 23);
    }
}

I have already searched and found that widening priority is higher than unboxing, so in above method invocation, first method should have been called because second parameter is same for both. But this does not happen. Can u plz explain?

Downtime answered 11/4, 2014 at 18:53 Comment(10)
It compiles for me - which compiler are you using? (And which version?)Horsley
Those prioritization happens in independent steps. First direct, then with widening, then unboxing.Prawn
@Sotirious Delimanolis can you plz elaborate your answerDowntime
This fails to compile in IntelliJ, but works fine with javac. JDK version 1.8Weatherboard
Fails to compile on JDK 7 too. Seems like a bug that has been fixed in Java 8.Weatherboard
Ok if i assume it works fine in jdk 1.8, can you still explain the reason why it is correct ?Downtime
Is this an original question or did you get it from somewhere? It looks kind of familiar.Springhead
@Cybernetic TwerkGuruOrc i was workin on method overloading, so made it by my own. But now i am stuck with it.Downtime
@Jeroen Vannevel can you plz help me with this question ...Downtime
This is a special case scenerio where autoboxing involves not only boxing an int to an Integer, but also involves a widening conversion to a Number.Changsha
K
8

It fails to compile in JDK 1.5, 1.6 and 1.7, but works in JDK 1.8.

Update: It seems like the fact that it worked with the first JDK8 versions was actually a bug: It worked in JDK 1.8.0_05, but according to this question and the answer by medvedev1088, this code will no longer compile in 1.8.0_25, which is the behavior that conforms to the JLS

I don't think that this is a bug that was fixed. Instead, it's rather an effect of the changes that are related to the method invocation mechanisms for lambda expressions in Java 8.

Most people would probably agree that the section about "Method Invocation Expressions" is by far the most complex incomprehensible part of the Java Language Specification. And there is probably a whole team of engineers concerned with cross-checking and validating this section. So any statement or any attempted reasoning should be taken with a huge grain of salt. (Even when it comes from the aforementioned engineers). But I'll give it a try, to at least flesh out the relevant parts that others may refer to for a further analysis:

Considering the section about

and considering that both methods are "Potentially Applicable Methods" ( JLS7 / JLS8 ), then the relevant subsection is the one about

For JLS 7, it states

The method m is an applicable variable-arity method if and only if all of the following conditions hold:

  • For 1 = i < n, the type of ei, Ai, can be converted by method invocation conversion to Si.
  • ...

(The other conditions are referring to forms of invocation that are not relevant here, e.g. invocations that really use the varargs, or invocations that involve generics)

Referring to the example: A method is applicable for the actual argument expression b of type Byte when b can be converted to the respective formal method parameter via method invocation conversion. According to the corresponding section about Method Invocation Conversion in JLS7, the following conversions are allowed:

  • an identity conversion (§5.1.1)
  • a widening primitive conversion (§5.1.2)
  • a widening reference conversion (§5.1.5)
  • a boxing conversion (§5.1.7) optionally followed by widening reference conversion
  • an unboxing conversion (§5.1.8) optionally followed by a widening primitive conversion.

Obviously, there are two methods that are applicable according to this specification:

  • m(Number b, Number ... a) is applicable via widening reference conversion
  • m(byte b, Number ... a) is applicable via unboxing conversion

You mentioned that you "...found that widening priority is higher than unboxing", but this is not applicable here: The conditions listed above do not involve any "priority". They are listed as different options. Even if the first method was void m(Byte b, Number ... a), the "identity conversion" would be applicable, but it would still only count as one possible conversion, and cause an error method due to the ambiguity.


So, as far as I understood, this explains why it did not work with JDK7. I did not figure out in detail why it did work with JDK8. But the definition of applicability of variable arity methods changed slighly in Identify Methods Applicable by Variable Arity Invocation in JLS 8:

If m is not a generic method, then m is applicable by variable arity invocation if, for 1 ≤ i ≤ k, either ei is compatible in a loose invocation context with Ti or ei is not pertinent to applicability (§15.12.2.2).

(I did not yet dive deeper into the definitions of "loose invocation contexts" and the section §15.12.2.2, but this seems to be the crucial difference here)


An aside, once more referring to your statement that you "...found that widening priority is higher than unboxing": This is true for methods that do not involve varargs (and that do not require method invocation conversion at all). If you left out the varags in your example, then the process of finding the matching method would start in Phase 1: Identify Matching Arity Methods Applicable by Subtyping. The method m(Number b) would then already be applicable for the parameter Byte b due to Byte being a subtype of Number. There would be no reason to go into Phase 2: Identify Matching Arity Methods Applicable by Method Invocation Conversion. In this phase, the method invocation conversion via unboxing from Byte to byte would apply, but this phase is never reached.

Kippy answered 12/4, 2014 at 13:6 Comment(4)
Here is similar question #30131220Pyroxylin
@Pyroxylin I still did not study the details of the updates related to "invocation contexts" etc. But now, according to the question (and your answer) that you linked to, it seems like the fact that the code from here did work with JDK8 was a bug in the earlier JDK8 versions, and that this bug was fixed in version 8-25 - do you agree?Kippy
Yep I agree. Just tested on JDK 1.8.0_25 and it doesn't compile. And it shouldn't according to JLS since there is no way for the compiler to determine which method is more specific. Also it raises an important question: whether the JLS should be improved and how it can be improved. Some compiler errors are counterintuitive such as the one from the question I linked. What do you think?Pyroxylin
@Pyroxylin As mentioned in the above answer: This part of the JLS is probably the most complicated one, and "improving" the JLS in this regard is difficult. Any attempt to simplify it may open the door for ambiguities, and, on the other hand, trying to make it more clear and point out certain implications of what it currently says may make it even more complicated. (And we're still talking about "trivial" cases here: The really nasty parts only come into play when generics are involved!). So I'm not sure how to do this. (BTW: I updated this answer with a link to the other one)Kippy

© 2022 - 2024 — McMap. All rights reserved.