Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Varargs Java Ambiguous Call

I'm a little confused about Java's varargs methods:

public static int sum(int ...a) {     return 0; }  public static double sum(double ...a) {     return 0.0; } 

When I tried to invoke sum() without passing any argument, then the int version of method was invoked. I don't understand why; normally the compiler must raise an error.

By contrast, the following piece of code generates a compiler error when I try to invoke sum without any argument:

public static int sum(int ...a) {     return 0; }  public static boolean sum(boolean ...a) {     return true; } 
like image 497
KarimS Avatar asked Jul 25 '15 14:07

KarimS


People also ask

How do you overcome ambiguity in Java?

The inclusion of generics gives rise to a new type of error that you must guard against ambiguity. Ambiguity errors occur when erasure causes two seemingly distinct generic declarations to resolve to the same erased type, causing a conflict. Here is an example that involves method overloading.

Is it good to use varargs in Java?

Varargs are useful for any method that needs to deal with an indeterminate number of objects. One good example is String. format . The format string can accept any number of parameters, so you need a mechanism to pass in any number of objects.

Can Varargs method be overloaded?

Also, Varargs methods can be overloaded if required.


2 Answers

The general rule that applies here is this: if one method signature is strictly more specific than the other, then Java chooses it without an error.

Intuituively, a method signature is more specific if you could delete it entirely and the other, less specific one would be applicable to each existing invocation.

When presented with a choice between the signatures sum(int... args) and sum(double... args), the signature sum(int... args) is more specific because any invocation of that method could also be passed on to sum(double... args) by applying a widening conversion. The same does not hold for a sum(boolean... args) method, which cannot be similarly converted.

Java Language Specification, SE 8 version:

15.12. Method Invocation Expressions

15.12.2.5. Choosing the Most Specific Method

The Java programming language uses the rule that the most specific method is chosen.

...

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:

...

  • m2 is not generic, and 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).

...

A type S is more specific than a type T for any expression if S <: T (§4.10).


4.10. Subtyping

4.10.1. Subtyping among Primitive Types

double >1 float

float >1 long

long >1 int

like image 122
Marko Topolnik Avatar answered Sep 28 '22 04:09

Marko Topolnik


As mentioned in this answer, there are rules followed when selecting which overloaded method to use.

To quote:

  1. Primitive widening uses the smallest method argument possible
  2. Wrapper type cannot be widened to another Wrapper type
  3. You can Box from int to Integer and widen to Object but no to Long
  4. Widening beats Boxing, Boxing beats Var-args.
  5. You can Box and then Widen (An int can become Object via Integer)
  6. You cannot Widen and then Box (An int cannot become Long)
  7. You cannot combine var-args, with both widening and boxing.

(Let's redefine rule 1 like so: "Primitive widening uses the most specific method argument as possible.")

So with these rules in mind we can get an idea of what's going on here:

According to rule number one, primitive widening uses the most specific method argument as possible. Since an int is representing by a non-decimal number (e.g. 1) and a double is represented by a decimal-number with precision 32 bytes more than that of a float (e.g. 1.0), we can say that ints are "less than" or "smaller than" doubles, and by that logic, ints can be "promoted" to doubles and doubles can be "demoted" to ints.

Put simply, a primitive that can be widened to another primitive (e.g. int -> float -> double) is more specific than another. For example, an int is more specific than a double because 1 can be promoted to 1.0.

When you passed in no arguments to these overloaded vararg methods of the same name, since the return is effectively the same (0 and 0.0 respectively), the compiler would choose to use the method that takes in a vararg of type int since it is more specific.

So, then, when you introduced these same methods that take in ints and booleans (types that cannot be widened to each other) respectively, the compiler now cannot choose a method to use since ints cannot be "promoted" or "demoted" like ints, floats and doubles. Therefore, it will throw a compile error.

I hope this helps you to understand what's happening.

like image 20
hmc_jake Avatar answered Sep 28 '22 06:09

hmc_jake