When passing arguments to methods become ambiguous in Java? - java

When method overloading is done, I know we can only create methods with the same name if only their method signatures are different.
class Demo{
public static void myMethod(int y, double x){}
public static void myMethod(double x,int y){}
public static void main(String args[]){
byte b=10;
myMethod(b,b);
}
}
Code shown above gives an error saying error: reference to myMethod is ambiguous
This problem occurs because the byte value can be assigned to both int and double types after automatic conversion and it is confusing as to which method the values to be passed right?
Please correct me if i'm wrong..
I tried the following program. I thought this would also give an error but it compiled without an error
class MyClass{
public static void myMethod(int i){
System.out.println("myMethod1(int)");
}
public static void myMethod(double a){
System.out.println("myMethod2(int)");
}
}
class Demo{
public static void main(String args[]){
MyClass.myMethod(100);
}
}
I thought it would also give the same error as earlier but this gave the output as myMethod(int)... so i assumed that since it has a perfectly matching method that can pass the int value, it doesn't give an error..
but what if i make the following changes to the second program above, why doesn't it give an error??
class MyClass{
public static void myMethod(int i){
System.out.println("myMethod1(int)");
}
public static void myMethod(double a){
System.out.println("myMethod2(int)");
}
}
class Demo{
public static void main(String args[]){
byte b=10;
MyClass.myMethod(b);
}
}
the byte can be automatically converted into int and double right? the output was given as myMethod(int)..
shouldn't this be confusing to the compiler and give that error reference to myMethod is ambiguous??

I will not specify and detail all rules used by the compiler to decide which method has to be invoked as it encloses other criteria.
I will only focus on the method parameters criteria that is your question.
In 15.12.2.5. Choosing the Most Specific Method, you have a precious information :
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 type error.
In your example here is the part of the JLS that should answer to your question :
One fixed-arity member method named m is more specific than another
member method of the same name and arity if all of the following
conditions hold:
The declared types of the parameters of the first member method are T1, ..., Tn.
The declared types of the parameters of the other method are U1, ..., Un.
If the second method is generic, then let R1 ... Rp (p ≥ 1) be its type parameters, let Bl be the declared bound of Rl (1 ≤ l ≤ p), let
A1 ... Ap be the type arguments inferred (§15.12.2.7) for this
invocation under the initial constraints Ti << Ui (1 ≤ i ≤ n), and let
Si = Ui[R1=A1,...,Rp=Ap] (1 ≤ i ≤ n).
Otherwise, let Si = Ui (1 ≤ i ≤ n).
For all j from 1 to n, Tj <: Sj.
If the second method is a generic method as described above, then Al <: Bl[R1=A1,...,Rp=Ap] (1 ≤ l ≤ p).
Which should interest you is For all j from 1 to n, Tj <: Sj.
At compile-time, if several applicable methods have been identified, then the most specific one is chosen.
Nevertheless, if more than one method have the maximal specificity with the effective parameter types, the compiler doesn't know what method should be invoked. So it emits a compilation error.
You can find this information in the JLS : 15.12.2. Compile-Time Step 2: Determine Method Signature:
A method is said to be maximally specific for a method invocation if
it is accessible and applicable and there is no other method that is
applicable and accessible that is strictly more specific.
If there is exactly one maximally specific method, then that method is
in fact the most specific method; it is necessarily more specific than
any other accessible method that is applicable. It is then subjected
to some further compile-time checks as described in §15.12.3.
It is possible that no method is the most specific, because there are
two or more methods that are maximally specific. In this case:
- If all the maximally specific methods have override-equivalent (§8.4.2) signatures, then:
If exactly one of the maximally specific methods is not declared
abstract, it is the most specific method.
Otherwise, if all the maximally specific methods are declared
abstract, and the signatures of all of the maximally specific methods
have the same erasure (§4.6), then the most specific method is chosen
arbitrarily among the subset of the maximally specific methods that
have the most specific return type.
However, the most specific method is considered to throw a checked
exception if and only if that exception or its erasure is declared in
the throws clauses of each of the maximally specific methods.
- Otherwise, we say that the method invocation is ambiguous, and a
compile-time > error occurs.
If we apply these rules to your three examples (I changed the order to start with cases that compile fine and finish with the compilation error case).
1) One maximally specific method found
class MyClass{
public static void myMethod(int i){
System.out.println("myMethod1(int)");
}
public static void myMethod(double a){
System.out.println("myMethod2(int)");
}
}
class Demo{
public static void main(String args[]){
MyClass.myMethod(100);
}
}
the compiler sees a method with a perfect matching : myMethod(int i) as the value passed is an int. It compiles fine.
2) One maximally specific method found
class MyClass{
public static void myMethod(int i){
System.out.println("myMethod1(int)");
}
public static void myMethod(double a){
System.out.println("myMethod2(int)");
}
}
class Demo{
public static void main(String args[]){
byte b=10;
MyClass.myMethod(b);
}
}
The compiler sees a method with a more higher specificity than the other one.
An implicit conversion from byte to int is indeed more specific than an implicit conversion from byte to double according to widening primitive conversions rules.
We could check that void myMethod(int i) is more specific than void myMethod(double a)if any invocation handled by the first method can be passed on to the other one without a compile-time type error.
myMethod(3); applied to void myMethod(double x) compiles fine.
But myMethod(double)3); applied to void myMethod(int y) produces a compilation error.
So a unique maximally specific method was found : void myMethod(int i).
The compilation is fine.
3) Unique maximally specific method not found
class Demo{
public static void myMethod(int y, double x){}
public static void myMethod(double x,int y){}
public static void main(String args[]){
byte b=10;
myMethod(b,b);
}
}
The compiler sees two methods where no one has a specificity higher than the other one.
First, in both cases, implicit conversions of the effective parameters types to the declared types of methods parameters is required.
But in both cases, the specificity is the same.
We could check that void myMethod(int y, double x) is as specific as myMethod(double x,int y) if any invocation handled by the any method cannot be passed on to the other one without a compile-time type error.
myMethod(double)3,4); applied to void myMethod(int y, double x) produces a compilation error as an int variable cannot accept a double value.
And myMethod(3,(double)4); applied to void myMethod(double x,int y) produces a compilation error for the same reason.
No unique maximally specific method was found. So the compiler cannot guess which method should be called. A compilation error occurs.

class MyClass{
public static void myMethod(int i){
System.out.println("myMethod1(int)");
}
public static void myMethod(double a){
System.out.println("myMethod2(int)");
}
}
class Demo{
public static void main(String args[]){
MyClass.myMethod(100);
}
}
The output is myMethod1(int) because the default type for java integer literal is integer. So it takes the closest match and calls myMethod(int i).
class MyClass{
public static void myMethod(int i){
System.out.println("myMethod1(int)");
}
public static void myMethod(double a){
System.out.println("myMethod2(int)");
}
}
class Demo{
public static void main(String args[]){
byte b=10;
MyClass.myMethod(b);
}
}
This wouldn't create an ambiguity for compiler as the parameter you are sending is byte, byte is widened to match with the method parameter. Had you declared myMethod(short i), It would have called this instead of myMethod(int i). This is how widening works in java.
byte->short->int->long
class Demo{
public static void myMethod(int y, double x){}
public static void myMethod(double x,int y){}
public static void main(String args[]){
byte b=10;
myMethod(b,b);
}
}
The compiler finds an ambiguity in the above snippet as byte b is widened to int now both the methods have parameter declaration as (double,int) (int,double) so there is an ambiguity as both the declaration has an int variable hence it creates a confusion for the compiler itself. Try to change one of the method declaration to (double,double) and you'll see that it calls the one that hasint` in the declaration. Example of what I am saying is
class Demo{
public static void myMethod(double y, double x){}
public static void myMethod(double x,int y){}
public static void main(String args[]){
byte b=10;
myMethod(b,b);
}
}
In this case it will call myMethod(double x, int y)
For more clearance you can refer JLS

It knows to prefer promotion to int over promotion to double, but you've given it a choice between two methods that would both require a promotion to int, so it doesn't know which one you mean.

To understand this kind of problems you should have below threes in mind:
The compiler always tries to choose the most specific method available with least number of modifications to the arguments.
Java designers have decided that old code should work exactly as it used to work before boxing-unboxing functionality became available.
Widening is preferred to boxing/unboxing (because of the above), which in turn, is preferred over var-args.

Related

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

The method is ambiguous for the type Error

I am trying to understand how Overloading in JAVA works and trying to get grasp of various overloading rules that are applied in case of widening, autoboxing and varargs in JAVA. I am not able to understand what is happening in the following scenario:
package package1;
public class JustAClass {
public static void add(int a, long b) {
System.out.println("all primitives");
}
//public static void add(Integer a, long b) {
// System.out.println("Wraper int, primitive long");
//}
public static void add(int a, Long b) {
System.out.println("Primitive int, Wrapper long");
}
public static void add(Integer a, Long b){
System.out.println("All wrapper");
}
public static void main(String[] args) {
int a = 10;
Integer b = 10;
long c = 9;
Long d = 9l;
add(a,c);
add(a,d);
add(b,c);
add(b,d);
}
}
At this point, I get a compilation error at the third invocation of the add method saying The method is ambiguous for the type Error .
Why is this so? What are the rules for determining which invocation of method will work? What is exactly happening in the following case?
I feel that fourth overloaded add method should work. Please help me understand the concept behind this.
There are 3 stages to method overloading resolution. The first stage doesn't do auto-boxing/unboxing, which means methods that require boxing/unboxing of the passed parameters in order to match one of the overloaded versions of add will only be considered if no match was found that doesn't require boxing/unboxing. That's why 3 of your calls, which have a single exact match, work. Regarding add(b,c);, see below why it's ambiguous.
add(a,c); // exact match to add(int a, long b)
add(a,d); // exact match to add(int a, Long b)
add(b,c); // there is no exact match, so at least one of the passed parameters must
// be boxed or unboxed. However, by unboxing b to int or boxing
// c to Long, each of the three add methods can match, and the
// compiler doesn't know which one to prefer
add(b,d); // exact match to add(Integer a, Long b)

Can please anybody explain me this Autoboxing?

Alright, here's the code :
public class Dec26 {
public static void main(String args[]) {
short a1=6;
new Dec26.go(a1);
new Dec26.go(new Integer(7));
}
void go(Short x){System.out.println("S");}
void go(Long x){System.out.println("L");}
void go(int x){System.out.println("i");}
void go(Number n){System.out.println("N");}
}
Why is the output "iN" and not "ii" ?
The Java compiler applies unboxing when an object of a wrapper class is:
Passed as a parameter to a method that expects a value of the
corresponding primitive type.
Assigned to a variable of the corresponding primitive type.
So, as there was a suitable method for Integer class, which is void go(Number n) because Number class is super class of Integer and this method accepts Integer objects as well.
So compiler didn't need to unbox the Integer to int.
Compiler chooses the closest match without autoboxing /unboxing first. It found go(Number) and did not use unboxing.

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

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

Categories