Why is this method overloading ambiguous?

2019-01-17 15:30发布

问题:

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?

回答1:

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

  • Method Invocation Expressions in JLS 7
  • Method Invocation Expressions in JLS 8

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

  • Phase 3: Identify Applicable Variable Arity Methods in JLS7
  • Phase 3: Identify Methods Applicable by Variable Arity Invocation in JLS8

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.