Overloading methods with var-args - combined with boxing and widening - java

When overloading methods that contain parameters that dont match, the JVM will always use the method with the smallest argument that is wider than the parameter.
I have confirmed the above with the following two examples:
Widening: byte widened to int
class ScjpTest{
static void go(int x){System.out.println("In Int");}
static void go(long x){System.out.println("In long");}
public static void main (String[] args){
byte b = 5;
go(b);
}
}
Boxing: int boxed to Integer
class ScjpTest{
static void go(Integer x){System.out.println("In Int");}
static void go(Long x){System.out.println("In Long");}
public static void main (String[] args){
int b = 5;
go(b);
}
}
Both the above examples output "In Int" which is correct. I am confused though when the situation involve var-args as shown in the following example
class ScjpTest{
static void go(int... x){System.out.println("In Int");}
static void go(long... x){System.out.println("In lInt");}
public static void main (String[] args){
byte b = 5; //or even with: int b = 5
go(b);
}
}
The above produces the following error:
ScjpTest.java:14: reference to go is ambiguous, both method go(int...) in ScjpTest and method go(long...) in ScjpTest match
go(b);
^
1 error
Why does it not apply the same rule as in the previous examples? i.e. widen the byte to an int as it is the smallest that is larger than byte?

var-args syntax is just a alias to passing array as an argument:
foo(int ... arg) is equal to foo(int[] arg)
But arrays are not hierarchical. String[] is not a subclass of Object[]. Exactly the same rule is relevant for the method arguments. Therefore compiler cannot distinguish between 2 overloaded methods that accept long[] and int[] when you are passing byte.

As AlexR pointed out, var-args is just like an array. Arrays of primitives (such as byte[] short[] int[] long[] float[] double[] seem to be internally compiled to the same class. That's why your overloaded methods are ambiguous. However the following code is perfectly valid:
static void go(int... x){System.out.println("In Int");}
static void go(Long... x){System.out.println("In lInt");}
This compiles successfully (since int[] and Long[] are different types), and produces the output In Int.
If you're preparing for SCJP, I would highly recommend you reading book SCJP Sun Certified Programmer for Java 6 Exam 310-065. The section Overloading in this book covers all the tricks with mixing boxing and var-args.

It actually works in Java 7: it returns "In Int" for the varargs example too. I guess it was just a missing feature in previous versions. I don't know what Java version you are using but maybe it is also working for Java 6.
However I must say that I was surprised that even your first example works (without the varargs). I was not aware of primitive widening conversions.
By the way, your first and last examples fail if you instead use Byte, Integer and Long since there is no hierarchy between those types (except that they are all subclasses of Number).

Related

Why compiler thinks `byte...` and `char...` are ambigious [duplicate]

This question already has answers here:
Method overloading with variable arguments (varargs) [duplicate]
(4 answers)
Varargs Java Ambiguous Call
(2 answers)
Closed 4 years ago.
Similar question about var-args in method signatures was asked few times (1, 2) but there is a corner case I don't get it. The compiler can distinguish between int... and long... overloaded method signatures and calls the method with smaller type.
However byte and char are clearly not the same size, yet the compiler complains that below test() method is ambiguous:
static void test(byte... v) { System.out.println("Byte"); }
static void test(char... v) { System.out.println("Char"); }
public static void main(String[] args) {
test(); // Error:(7, 5) java: reference to test is ambiguous
// both method test(byte...) in App and method test(char...) in App match
}
Same happens for short... and char... however int... and char... are not ambiguous.
Why is char... considered ambiguous with byte... or short... while by themselves byte... and short... are distinguishable in method signature?
static void test(byte... v) { System.out.println("Byte"); }
static void test(short... v) { System.out.println("Short"); }
public static void main(String[] args) {
test(); // Byte
}
You didn't include any arguments in the call so how would the compiler know the type?
Also
"Otherwise, if the operand is of compile-time type byte, short, or char, it is promoted to a value of type int by a widening primitive conversion (ยง5.1.2)."
https://docs.oracle.com/javase/specs/jls/se10/html/jls-5.html#jls-5.6
The compiler does not see the types as equivalent. It promotes them to int. Since both forms promote to int it doesn't know which to use.
Try explicitly casting the arguments to resolve the ambiguity.
The test function is overloaded. You need to pass an appropriate parameter so that the compiler may infer which method to call. Additionally, both byte and char are equally specific primitives for varargs (by that I mean compiler treats them similarly) which thereby causes ambiguity.

type conversion at method calling

public class Demo1{
public static void main(String[] args){
show('A','A');
}
public static void show(char c, long a){
System.out.println("long-char");
}
public static void show(char c, int a){
System.out.println("char-int");
}
}
Output : char-int
But when I change the order of parameters in the first show() method (replacing
public static void show(char c, long a){} with public static void show(long a, char c) {}), I get a compilation error.
The compiler says that it is an ambiguous method call, because it is.
The general approach taken for overload resolution is to find the most specific applicable method, given the number and types of the actual parameters.
In the first case, the two methods have char as their first parameter; so it is only down to choosing whether the int or long overload is more specific, given that the actual parameter is a char: it is the int overload which is more specific, because int is narrower than long.
In the second case, one method has char as the first parameter; one method has char as the second parameter. So, given that the actual parameters are both chars, one of the parameters has to be converted (widened) to invoke either of the methods.
The language spec does not define that one is more specific than the other in such a case; they are both considered equally applicable, so the method call is ambiguous, and thus is a compile-time error.

Java varargs method overloading compiler error - ambiguity?

So, today I've been testing Java's overloading techniques and I've come across ambiguity which I can't explain. Basically, when there is a vararg method with primitive and its corresponding wrapper the compiler complains and can't decide which one to choose and I don't understand why? It's easy for human to decide and not for compiler?
Here is the fragment which works for non-vararg parameters:
public static void main(String[] args)
{
int a = 14;
Integer b = new Integer(14);
stuff(a);
stuff(b);
}
static void stuff(Integer arg) { System.out.println("Integer"); }
static void stuff(int arg) { System.out.println("int"); }
And here comes the vararg which complains and cries like a baby:
public static void main(String[] args)
{
int a = 14;
Integer b = new Integer(14);
stuff(a); // Doesn't compile (ambiguity)
stuff(b); // Doesn't compile (ambiguity)
}
static void stuff(int... arg) { System.out.println("varargs int"); }
static void stuff(Integer... arg) { System.out.println("varargs Integer"); }
Consider the following two hypothetical calls to stuff():
int a = 14;
Integer b = new Integer(14);
stuff(a, b);
stuff(b, a);
How does the compiler even know which method should be called here? Because of autoboxing rules, either call could be referring to either overloaded method.
Update:
My answer is logically correct, or at least on the right track, but for a more formal answer we can refer to this SO question:
Why ambiguous error when using varargs overloading with primitive type and wrapper class?
The two varargs method are invoked in loose invocation context. As a result, the compiler will attempt to find the more specific method via JLS 15.12.2.5 Choosing the Most Specific Method. However, since neither int nor Integer are subtypes of one another, the compiler will throw an error.
The problem is:
java is doing behind the scenes bridge methods (you need to verify that is you need deep info)
AND the important part, vargargs means too YOU CAN JUST NOT PASS ANY PARAMETER, so:
static void stuff(int... arg)
and
static void stuff(Integer... arg)
can both being invoked taking no parameters... so that will create some conflict about what method should the JVM invoke

final casting concept doesn't apply for overloading

In my casting class, teacher taught us an interesting fact as follows.
class Casting {
public static void main(String args[]){
int i = 10;
byte b = i;
System.out.println(b);
}
}
We got an error
java:5: possible loss of precision
And then we changed the code as follows
class Casting1 {
public static void main(String args[]){
final int i = 10;
byte b = i;
System.out.println(10);
}
}
10
We got the correct output. As for the reason, he told that when we modify a variable final the variable is stored in the smallest data type possible. In this case was a byte. That's the reason that we were able to cast that without using the cast keyword.
But when we use method overloading like this,
class A {
void m(int i){
System.out.println("int");
}
void m(byte b){
System.out.println("byte");
}
public static void main(String args[]){
A a1 = new A();
final int i = 10;
a1.m(i);
}
}
I get the output int. If the final variables are stored in the lowest possible data type, it should be byte. So I tried the following code without overloading.
class A {
void m(byte b){
System.out.println("byte");
}
public static void main(String args[]){
A a1 = new A();
final int i = 10;
a1.m(i);
}
}
java:9: m(byte) in A cannot be applied to (int)
What's the reason for this? Is there any point that I have misunderstood?
You are mixing up memory space of variables and their type.
Calling the method m(...) will first of all check the type of the paramether variable. Here it is an int so it will chose the according overloaded method, no matter the size of the int in memory.
Although I really apreciate you first example that brings the light into one of the characteristics of the final identifier.
This bit isn't quite correct...
"As for the reason, he told that when we modify a variable final the variable is stored in the smallest data type possible. In this case was a byte."
It doesn;t store it as a byte, it stores it as an int, BUT it is effectively a constant so when Java compiles the line byte b = i; it knows for sure that the value will be 10, which doesn't need casting.
Is there any point that I have misunderstood?
Yes. The search for which method to apply depends on the types of the arguments. Unlike the case of assignments, there's no conversion attempt for method arguments (at least, there wasn't before autoboxing was added to the language, which adds another set of arbitrary rules).
If the conversion is part of an assignment, and the value can fit into a byte, the compiler performs the conversion automatically for you.
The JLS clearly explains that is a special case that only applies to assignment and not to conversions in other contexts.
It's worth mentioning that byte is useful only when you program for embedded devices or dealing with files/networks. byte and int occupy the same space because variables addresses are aligned.

Overloading methods

I saw below question posted on this site.
"What happens when we pass int arguments to the overloading method having float as a parameter for one method and another having double param".
I thought I understood the concept and wrote this code:
public class TestClass {
public static void main(String args[])
{
TestClass t=new TestClass();
t.sum(1/4);
}
void sum(double d)
{
System.out.println("Double==="+d);
}
void sum(int i)
{
System.out.println("Integer==="+i);
}
void sum(short s)
{
System.out.println("Short==="+d);
}
}
According to my understanding explained on this site (as mentioned above), I thought it will print Short===0, but to my surprise it prints Integer===0. Can any one explain this to me?
First of all, these are overloaded methods, not overridden methods.
1 and 4 are integers. Therefore 1/4 is an integer division, returning 0.
Therefore, the method being called is sum(int i).
sum(short s) would never be called for an int parameter, since that would require a narrowing primitive conversion (JLS 5.1.3), that may cause data loss, and is not allowed in method invocation conversion (JLS 5.3). Such a conversion can be done with an explicit cast.
If you remove the int version, sum(double d) would be called, and if you remove the double version, the code won't compile.
In order to call the short version, you must cast the parameter to short :
t.sum ((short)(1/4));
If you don't explicitly tell the compiler what are the types of 1 and 4, it assumes they are of type int. Then, / operator will apply integer division and will produce another int (which will be 0.)
After that, the method with the most specific to integer parameter type will be invoked. In your case, this will be sum(int i).
If you want to invoke some of the other overloaded methods, you will have to explicitly:
do a cast. For example, sum((short) (1/4)); will invoke sum(short s) due to the cast.
point the type of the operands. For example, sum(1d/4) will invoke sum(double d), since 1d/4 will result to double
For integer number, the type int is a default choice. So, although 1 and 4 can be defined as both int or short, since you did not defined anything, the compiler identified 1 and 4 as int and therefore it entered into the function for 1/4 division (0), which took the parameter int.

Categories