I have a question related to the following code snippet:
class VarArgsTricky {
static void wide_vararg(long... x) {
System.out.println("long...");
}
static void wide_vararg(Integer... x) {
System.out.println("Integer...");
}
public static void main(String[] args) {
int i = 5;
wide_vararg(i, i, i); // needs to widen and use var-args
Long l = 9000000000l;
wide_vararg(l, l); // prints sucessfully "long..."
}
}
The first call to wide_vararg fails to compile(saying that the method is ambigous) while the second compiles just fine.
Any explanations about this behaviour?
Thanks!
The first wide_vararg call is ambiguous because the compiler could either:
widen the ints to longs, and call the first wide_vararg method, or
autobox the ints to Integers, and call the second wide_vararg.
It doesn't know which it should do, however, so it refuses to compile the ambiguous method call. If you want the first call to compile, declare i as an Integer or a long, not an int.
When a var-arg method is invoked, the parameters get converted into an array of that type at compile time.
In the first call, the parameters get converted to an int[]. As all arrays in Java are direct sub types of the Object class, the concept of primitive widening does not apply in which case, both the overloads become equally applicable because long[] and Integer[] are at the same level. Hence the ambiguity
Related
In following piece of code
class Main {
void m1(double x){
System.out.println("double");
}
void m1(float x){
System.out.println("float");
}
void m1(long x){
System.out.println("long");
}
void m1(byte x){
System.out.println("byte");
}
void m1(short x){
System.out.println("short");
}
void m1(int x){
System.out.println("int");
}
public static void main(String[] args) {
Main m = new Main();
m.m1(1);
}
}
Why is the output "int" instead of "byte" or "long" or "short" or "float" or "double"?
If by following Automatic conversion from "byte" -> "short" -> "int" -> "long" -> "float" -> "double" then output should print "double" right?
(https://www.geeksforgeeks.org/type-conversion-java-examples/)
Int literals (like 1) are expressions of type int.
Overload resolution proceeds in a number of stages, looking for a method which can accept the parameters with the types you are passing.
If there is a single overload where the actual parameters have the same types as the formal parameters, invoke that.
If there is a single non-varargs overload where the actual parameters can be automatically converted (e.g. by widening or un/boxing) to be of the same types as the formal parameters, invoke that.
If there is a single varargs overload where the actual parameters can be automatically converted to be of the same types as the formal parameters invoke that.
Otherwise, error.
These phases are applied in turn, continuing until a match is found.
Since there is an overload of m1 which takes int, a match is found in phase 1, so no conversion of that value to another type needs to happen.
The automatic conversion only happens when a method accepts a wider type than the input. For example, if you remove all the void m1(..) functions except the long version. Then you can pass in an integer, and the integer will be automatically widened to a long.
In your example, there is a method that accepts an integer, so java will use that method instead, and there is no widening happening.
By definition, that primitive literal is of type int. Any numeric literal without a decimal point is of type int, unless you put a l/L in it: 1L to turn it into a long. There is no 1 "byte" literal, you have to go (byte) 1 to get there.
The compiler looks for the best fit, and uses that method that takes an int.
That is all there is to this. If you want to see other methods to be invojed, either cast the value to (long) for example, or start using values such as 1.0 instead of 1.
Regarding your comment: that automatism only kicks in when required. But in your case: A) you are using an int value B) there is an int-taking method. The compiler doesn't turn ints into longs into doubles for no reason!
Primitives are at it again, breaking rules, I learned before. Well not technically primitive but composed of them.
I learned that whenever there's no method more specific than rest, compile time error occurs as it happens here.
public static void caller(){
z5(); // Error. Neither Integer, nor String is more specific
z5(null); // Error for the same reason
}
public static void z5(Integer...integers){
System.out.println("Integer z5 called");
}
public static void z5(String...strings){
System.out.println("String z5 called");
}
Now comes primitives into the picture.
public static void caller(){
z1(null); // Error cuz [I, [J, [F all are subclass of Object.
z1(); // SURPRISINGLY works and calls the int one. WHY?
}
public static void z1(int...integers){
System.out.println("int z1 called");
}
public static void z1(long...longs){
System.out.println("long z1 called");
}
public static void z1(float...floats){
System.out.println("float z1 called");
}
Expected compile time errors occurs here.
public static void caller(){
z1(null); // Error
z1(); // Error
}
public static void z1(int...integers){
System.out.println("int z1 called");
}
public static void z1(boolean...bools){
System.out.println("bool z1 called");
}
Now my question is, int[], float[], or any array of primitives are not primitive types then Why are they treated differently than other reference types?
--UPDATE--
#john16384 You don't think I read your "Possible duplicate" Varargs in method overloading in Java
Top answer there says You cannot combine var-args, with either widening or boxing. Besides I forgot to mention, OP's code posted there, works fine on my jdk 7.
What exactly is going on which works for (int...is) & (float...fs) but not for (Integer...is) & (Float...fs) and not for (int...is) & (boolean...bool)
Quote from the JLS about varargs invocations when multiple methods are applicable:
15.12.2.5. Choosing the Most Specific Method
If more than one member method is both accessible and applicable to a
method invocation, it is necessary to choose one to provide the
descriptor for the run-time method dispatch. The Java programming
language uses the rule that the most specific method is chosen.
The informal intuition is that one method is more specific than
another if any invocation handled by the first method could be passed
on to the other one without a compile-time error. In cases such as an
explicitly typed lambda expression argument (§15.27.1) or a variable
arity invocation (§15.12.2.4), some flexibility is allowed to adapt
one signature to the other.
The important part here is how methods are defined to be more specific. It basically says that int... is more specific than long... because any values you could pass to the first method could also be passed to the second method.
This will also apply to the case where you pass no arguments. int... will be the most specific (it will even see byte... as more specific!).
public static void main(String[] args) {
bla();
}
private static void bla(long... x) {}
private static void bla(int... x) {}
private static void bla(short... x) {}
private static void bla(byte... x) {} // <-- calls this one
The reason you get an error when also creating an overload boolean... is that it now is ambigious which one to call, and the compiler stops before getting to the point where it has to pick the most specific method.
I was reading a java book, and came up with this example
public class Main {
public static void main(String[] args) {
play(4); // DOES NOT COMPILE
play(4L);
glide(1,4); // THIS ONE COMPILES
}
public static void play(Long l) { }
public static void play(Long... l) { }
public static void glide(long i, long j) {}
}
but didn't quite get the idea why doesn't java cast the int to long and call the first method, because it does the cast for the method with 2 parameters?
Because the method glide uses parameters of type long, and java easily casts int to long, but the first method uses parameter of type Long, which is the wrapper class for long type, not int, that's why the first method doesn't compile
The third method, glide, is an example of a widening cast from an int to a long which is done automatically.
The first method, play(Long l), accepts a Long (capital L) object. Primitives cannot be cast to objects which is why your first example doesn't compile, but the compiler will convert a primitive to its equivalent object via "autoboxing" which is why play (4L) works.
Note these rules:
You CANNOT widen and then box. (An int can't become a Long.)
You CANNOT widen from one wrapper type to another. (IS-A fails.)
You can box and then widen. (An int can become an Object, via Integer.)
Here is what I know about overload resolution in java:
The process of compiler trying to resolve the method call from given
overloaded method definitions is called overload resolution. If the
compiler can not find the exact match it looks for the closest match
by using upcasts only (downcasts are never done).
Here is a class:
public class MyTest {
public static void main(String[] args) {
MyTest test = new MyTest();
Integer i = 9;
test.TestOverLoad(i);
}
void TestOverLoad(int a){
System.out.println(8);
}
void TestOverLoad(Object a){
System.out.println(10);
}
}
As expected the output is 10.
However if I change the class definition slightly and change the second overloaded method.
public class MyTest {
public static void main(String[] args) {
MyTest test = new MyTest();
Integer i = 9;
test.TestOverLoad(i);
}
void TestOverLoad(int a){
System.out.println(8);
}
void TestOverLoad(String a){
System.out.println(10);
}
}
The output is 8.
Here I am confused. If downcasting was never to be used, then why did 8 get printed at all? Why did compiler pick up the TestOverLoad method which takes int as an argument which is a downcast from Integer to int?
The compiler will consider not a downcast, but an unboxing conversion for overload resolution. Here, the Integer i will be unboxed to an int successfully. The String method isn't considered because an Integer cannot be widened to a String. The only possible overload is the one that considers unboxing, so 8 is printed.
The reason that the first code's output is 10 is that the compiler will consider a widening reference conversion (Integer to Object) over an unboxing conversion.
Section 15.12.2 of the JLS, when considering which methods are applicable, states:
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.
The second phase (§15.12.2.3) performs overload resolution while allowing boxing and unboxing [...]
In Java, resolving methods in case of method overloading is done with the following precedence:
1. Widening
2. Auto-boxing
3. Var-args
The java compiler thinks that widening a primitive parameter is more desirable than performing an auto-boxing operation.
In other words, as auto-boxing was introduced in Java 5, the compiler chooses the older style(widening) before it chooses the newer style(auto-boxing), keeping existing code more robust. Same is with var-args.
In your 1st code snippet, widening of reference variable occurs i.e, Integer to Object rather than un-boxing i.e, Integer to int. And in your 2nd snippet, widening cannot happen from Integer to String so unboxing happens.
Consider the below program which proves all the above statements:
class MethodOverloading {
static void go(Long x) {
System.out.print("Long ");
}
static void go(double x) {
System.out.print("double ");
}
static void go(Double x) {
System.out.print("Double ");
}
static void go(int x, int y) {
System.out.print("int,int ");
}
static void go(byte... x) {
System.out.print("byte... ");
}
static void go(Long x, Long y) {
System.out.print("Long,Long ");
}
static void go(long... x) {
System.out.print("long... ");
}
public static void main(String[] args) {
byte b = 5;
short s = 5;
long l = 5;
float f = 5.0f;
// widening beats autoboxing
go(b);
go(s);
go(l);
go(f);
// widening beats var-args
go(b, b);
// auto-boxing beats var-args
go(l, l);
}
}
The output is:
double double double double int,int Long,Long
Just for reference, here is my blog on method overloading in Java.
P.S: My answer is a modified version of an example given in SCJP.
widening beats boxing, boxing beats var-args. In your example, the widening cannot happen, so the boxing it's applied and Integer is unboxed. Nothing unordinary.
Actually in the second example no downcasting is occurred. There occurred the following thing -
1. Integer is unwrapped/unboxed to primitive type int.
2. Then the TestOverLoad(int a) method is called.
In main method you declare Integer like -
Integer i = 9;
Then call -
test.TestOverLoad(i);
Whereas, you have 2 overloaded version of TestOverLoad() -
TestOverLoad(int a);
TestOverLoad(String a);
Here the second overloaded version of TestOverLoad() takes completely different argument String. Thats why Integer i is unboxed to a primitive type int and after that the first overloaded version is called.
All objects in Java extend the class Object, including the class Integer. These two class have the following relationship: Integer "is a(n)" Object because Integer extends Object. In your first example, the method with Object parameter is used.
In the second example, no methods are found that accept an Integer. In this case Java uses what is called auto-unboxing to resolve the Integer wrapper class to a primitive int. Thus, the method with the int parameter is used.
While accepted answer of #rgettman leads to very right source, more precisely, §15.12.2.2 and §15.12.2.3 of the JLS Section 15.12.2 discuss the applicability, not the resolution - what the OP asked for. In the example the OP provided both testOverLoad methods are applicable, .i.e. will be successfully resolved in the absence of another one.
Instead, 15.12.2.5. Choosing the Most Specific Method discusses the resolution of the applicable methods.
It reads:
One applicable method m1 is more specific than another applicable
method m2, for an invocation with argument expressions e1, ..., ek, if
any of the following are true:
...
m1 and m2 are applicable by strict or loose invocation, and where m1 has formal parameter types S1, ..., Sn and m2 has formal parameter
types T1, ..., Tn, the type Si is more specific than Ti for argument
ei for all i (1 ≤ i ≤ n, n = k).
So, in the first example, provided by OP, for parameter i of type Integer method testOverLoad(Object a) is more specific than testOverLoad(int a).
This is happening due to Widening and Narrowing Type casting
Widening means a small type can be accommodated in a larger type without any loss of information.
Widening Typecasting is automatic.
That means a byte value can be automatically casted to short, int, long or double.
byte->short->int->float->double
Widens from left to right.
Type Casting in Java
Hope this answers your question!
you can check with one more example :
public class HelloWorld {
void show(String c){
System.out.println("int double overloaded method");
}
void show(Object c){
System.out.println("double int overloaded method");
}
}
here the you will get : double int overloaded method
public class A {
public void test(Integer i) {
System.out.println("In Wrapper Method");
}
public void test(int i) {
System.out.println("In primitive Method");
}
public static void main(String args[]) {
A a = new A();
a.test(5);
}
}
When I will call test method from main and pass integer argument, then it will call the method which accept primitive type as argument. I just want to know that why it call primitive type method rather than the method who accepts wrapper class as argument? Is there any rule, which java follow to call methods?
Thanks,
As a rough "rule of thumb", Java uses the closest matching method for the declared types of the argument expressions when choosing between different overloads. In this case test(int i) is closer than test(Integer i) when the method is called as test(5), because the latter requires a type promotion (auto-boxing) to make the actual argument type correct.
In fact the decision making process is rather more complicated than this, but the "rule of thumb" works in most cases.
In this case, you are using the literal value 5, which is a primitive int in Java. Any bare number literal such as that in Java is a primitive. To call the other method would require passing new Integer(5) instead. The autoboxing that was introduced in Java 5 can blur the lines between the two, but they are still distinct from each other.
Not really an answer, but when overloading it shouldnt matter which method is called. In this case calling either method if the value is an Integer or int the result should be the same. Overloading should only be used to convert values to some other form, or as a mechanism to provide defaults.
String.valueOf() is a good example of converting inputs to a String.
a.test(5) // test(int i)
Compiler understands it as a primitive type. If you want run the other method you should send
a.test(new Integer(5)) // test(Integer i)
because Java select the closest matching method to run
You have passed 5 as primitive type so it will call the method that accepts primitive so if you want to call the one that accepts 5 as object then you have to first covert it to object as below
int a = 5;
Integer b = new integer(5);