overloading method priority in java

I know that this problem discussed many times but I don't understand anyway.

Research this code:

public class Main {  
    public static void var(Integer x, int y) {  
        System.out.println("Integer int");  
    }  


    public static void var(int... x) {  
        System.out.println("int... x");  
    }  

    public static void var(Integer... x) {  
        System.out.println("Integer...");  
    }  

    public static void main(String... args) {   
        byte i = 0;  
        Integer i2 = 127;  
        var(i, i2);  
    }  
} 

In my brain following rule:

  1. widening

  2. boxing

  3. boxing+varargs

According this rule I make next actions

1.byte wides to int

Now I have int Integer and there are exist method takes Integer and int

2.make boxing

Hence. int -> Integer and Integer -> int arguments

I think that arguments are applicable and expected to see

Integer int

in output.

But I see

int ...

why?


It is now clear that the method var(int...) is selected and not var(Integer...).

The reason is that only certain conversions are allowed to be applied, and it can only be one of these conversions from the list, not a chain of conversions. The java compiler is not allowed to do a widening primitive conversion first, and then a boxing conversion.

It's specified in the Java Language Specification in section 5.3

5.3. Method Invocation Conversion

Method invocation conversion is applied to each argument value in a method or constructor invocation (§8.8.7.1, §15.9, §15.12): the type of the argument expression must be converted to the type of the corresponding parameter.

Method invocation contexts allow the use of _one of_ the following:

  • 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.

The only option for the compiler is to do:

  1. a widening primitive conversion on the first argument
  2. an unboxing conversion on the second argument

That turns (byte, Integer) into (int, int).

It cannot first turn the first argument byte into an int and then apply a boxing conversion on the same argument from int to Integer because two conversions in sequence are not allowed.

Let's go back one step to find out how the compiler selects which overloaded method to invoke. That is described in JLS 15.12.2. (15.12.1 describes how to find the class or interface to search, but we already know that we want to invoke a static method in class Main)

The first two phases that a compiler goes through to select the right overloaded methoddo not apply to variable argument ("variable arity") methods, but the third phase does:

The third phase (§15.12.2.4) allows overloading to be combined with variable arity methods, boxing, and unboxing.

Section 15.12.4 is quite complicated, but the rules that apply here are:

  • first apply the rules for non-variable arity arguments (not applicable in your case)
  • each variable argument in the invocation must be convertable by method invocation conversion (the piece that I copied above) to the type of the variable argument declaration

So..

  1. you try to invoke a method named var with (byte, Integer)
  2. the compiler looks at your method var(Integer...)
  3. it asks: can I convert the first argument, a byte, to Integer (the declared type of the argument in the method)
  4. it looks at the rules in JLS 5.3. It can only apply one of the conversions from the list of 5. None of them can convert a byte to an Integer directly - it cannot do two steps.
  5. So the compiler decides that it cannot select var(Integer...)
  6. then it looks at your other method, var(int...)
  7. According to JLS 5.3, it can convert your first argument, the byte to an int using a widening primitive conversion. That's a check mark.
  8. Moving on to the second argument, your Integer, it sees that JLS 5.3 allows the compiler to convert it to an int using an unboxing conversion. So that's also a check mark.
  9. That was the last argument, so var(int...) is a good match.
  10. The compiler now moves on to see if there are more methods that match your invocation. If there are more that do, that will result in an ambiguous-invocation error.
  11. But there are no more methods with the name var, so var(int...) is the only applicable method. The compiler will now generate code to do the necessary conversions and invoke that method.

Java can only do "box and wide" not "wide and box". For example,

  • int --> Number [OK!]
    • int boxed to Integer widen to Number
  • byte --> Integer [DOESN'T COMPILE]
    • byte needs to widen to int first, then box to Integer. Java doesn't allow it. Pls note that you can't do this -byte box to Byte then widens to Integer (Integer is not super class of Byte).

So, in your given methods, first argument byte already fails the two Integer methods. So, only int... is applicable.

I wrote the following class for demo:

public class Overload{

    public static void primitiveWiden(int x){
        System.out.println("int");
    }
    public static void refWiden(Map m){
        System.out.println("Map");
    }
    public static void priWideAndBox(Integer o){//doesn't work
        System.out.println("Object");
    }
    public static void boxAndRefWide(Number n){//it works
        System.out.println("Number");
    }

    public static void main(String[] args){
        byte b =0;
        int i =0;
        HashMap m = new HashMap();

        primitiveWiden(b);
        refWiden(m);
        priWideAndBox(b);//compile error
        boxAndRefWide(i);

    }

}