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:
widening
boxing
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:
- a widening primitive conversion on the first argument
- 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..
- you try to invoke a method named
var
with (byte, Integer)
- the compiler looks at your method
var(Integer...)
- it asks: can I convert the first argument, a
byte
, to Integer
(the declared type of the argument in the method)
- 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.
- So the compiler decides that it cannot select
var(Integer...)
- then it looks at your other method,
var(int...)
- 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.
- 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.
- That was the last argument, so
var(int...)
is a good match.
- 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.
- 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);
}
}