domingo, 16 de janeiro de 2011

Ambiguous Varargs Declaration

Preparing to my certification OCPJP 6, I realized the inquisition mock exam provided on coderanch forum. This mock exam arrises a lot of doubts that I thought I woudn't have when doing my exam.
At this post, I will describe a really tricky situation. So tricky that it lead me to wrong two questions at this mock exam.

Take a look at the code that follows:


public class Test1 {
public static void main(String[] args) {
Test1 inq = new Test1();
inq.method(1, 1, 1); // < -- this code wont compile
}
public void method(Integer ...i) {
System.out.println("eye in the sky");
}
public void method(int... i){
System.out.println("fly in the pie");
}
}


According to the inquisition explanation:
"to the compiler Integer... and int... are pretty much the same. This results in ambiguous state, which is an error. The jvm wont know which method to call if this is allowed to be compiled."

When trying to compile the code above you can get the following error:
The method method(Integer[]) is ambiguous for the type Test1

So considering the code and explanation above when trying to solve another question that presents the code below I thought that it was the same situation... But... as I will show... It isn't the same. The next code, won't compile even if we aren't trying to use some overloaded method.


public class Ambiguous {
public static void test(int[] input) {
System.out.println("int[]");
}
public static void test(int... input) {
System.out.println("int...");
}
}


At the first case, the compiler claims when trying to use a method while at the last case, the compiler claims when declaring those methods.

For the first case, I found a good explanation at JSL - Java Specification Language - Third Edition.
The summary below, is a brief description of how a method name is resolved at compile time regarding the JSL.

"Determining the method that will be invoked by a method invocation expression involves three main steps:

  • Determine the Class or Interface to Search: figure out the name of the method and which class or interface to check for definitions of methods of that name. Methods will be figured out regarding the current class invoking the method, super classes, interfaces, enclosing classes (considering inner classes case).

  • Determine Method Signature: After determining where to take the method, the second step will consider the argument expressions to locate the method that are both accessible and applicable. The most specific method will be chosen when more than one method could be used. How to know if a method is applicable? The process of determining applicability begins by determining the potentially applicable methods. The remainder of the process is split into three phases (this division into phases is to ensure compatibility with older versions of the java programming language):

  • Phase 1: Performs overload resolution without permitting boxing or unboxing or the use of variable arity method invocation. If no applicable method is found, proceeds with second phase.
    Phase 2: Performs overload resolution allowing boxing and unboxing but prevents the use of varargs.This prevents the invocation of a method defined with varargs if a fixed arguments method is found. If no applicable method is found, proceeds with third phase.
    Phase 3: Allows overloading to be combined with varargs, boxing and unboxing.

  • Determine if the chosen method is appropriate

When determining the method to be used, more than one method could be maximally specific. In this case, the method invocation can be defined as ambiguous so a compile-time error occurs."

Considering the description above, the compile time error claiming for ambiguity, was thrown because of compatibility issues.
Let's take another look into the first code:


public class Test1 {
public static void main(String[] args) {
Test1 inq = new Test1();
inq.method(1, 1, 1); // ERROR LINE
}
public void method(Integer ...i) {
System.out.println("eye in the sky");
}
public void method(int... i){
System.out.println("fly in the pie");
}
}


Considering the ERROR LINE and phase 1 (from second step of JSL definition): boxing, unboxing and varargs are not permitted. So, in this case, where two methods are potentially applicable, the values 1, 1, 1 cannot be boxed, unboxed and cannot be used to call the varargs definition method, so let's go to phase 2 where the boxing and unboxing are permitted but varargs is not permitted yet. Even though we can box and unbox those values (1,1,1) it still needs to use a vararg version of the method. So, phase 2 fails too and the compiler goes to phase 3 where varargs can be applied. Well... using the vararg we still have 2 maximally specific methods which leads to a compile-time error.

So, this is the difference between the first case and second case where a compile-time error occurs when trying to use a method or a compile-time error occurs when declaring 2 overloaded methods.

The code could compile if it was wrote as follows:


public class Ambiguous {
public static void main(String[] args) {
Ambiguous inq = new Ambiguous();
inq.test(new Integer[]{1,2,3});
}
public static void test(Integer... input) {
System.out.println("Integer...");
}
public static void test(int... input) {
System.out.println("int...");
}
}


Considering the code above, the method will be determined in phase 1 where there is no need to box, unbox or use var-args since Integer[] and Integer... is the same thing at byte-code level.

Um comentário: