Varargs and Behavior of Method Arguments
Varargs
Java 5 brings a convenient new construct to Java that lets a method take zero or more arguments of a given primitive or type. It’s call variable arguments or varargs. In this article we’ll deal with some questions that the introduction of varargs brings up regarding the behavior of method arguments.
A varargs method can be called with zero, one or many individual arguments or with a single array. After the method is called, the arguments are accessed internally using the familiar square brackets array syntax. It looks like this:
public class BasicVarargs {
public static void main(String[] args) {
myMethod(); // zero arguments are fine
myMethod(0); // one argument is fine
myMethod(0, 1); // multiple arguments are fine
myMethod(new int[] {0, 1, 2}); // array of args is fine
}
static void myMethod(int... myArgs) {
System.out.println(myArgs[0]);
System.out.println(myArgs[1]);
// etc
}
}
The Issue
This raises a significant question if you’re taking the Sun Certified Java Programmer (SJCP) exam or just want to be knowledgeable about how method parameters are treated. A single argument (whether a primitive or an object reference) cannot be reassigned within a method. Passing a primitive into a method and assigning it a different value will leave the primitive in the calling method unchanged. Passing an object reference into a method and then reassigning the references will, likewise, leave the reference in the calling method unchanged. Objects however, including arrays, have an associated caveat. While the reference to the object cannot be reassigned, the method can use the copy of the reference to alter the content of the object. For a non-array object, this means its instance fields. For an array, this means the primitives or object references at each of its indices. That brings us to our question: If methods with varargs access those arguments as if they were an array, does that mean that they can change the value of a primitive or reassign an object reference that’s held at any given array index? That’s the issue we’re about to explore.
Review of Primitive Argument Behavior
As a quick review, what’s the output of the following?
public class PrimitiveArgBehavior {
static int myInt = 0;
public static void main(String[] args) {
System.out.println(myInt);
intMethod(myInt);
System.out.println(myInt);
}
private static void intMethod(int someInt) {
someInt++;
}
}
Output:
0
0
myInt remains unchanged. intMethod() works on a copy of myInt and that copy loses scope at the end of intMethod().
Review of Array Reference Argument Behavior
What’s the output of the following?
public class ArrayReferenceArgBehavior {
static int[] myIntArray = {0};
public static void main(String[] args) {
System.out.println(myIntArray[0]);
intArrayMethod(myIntArray);
System.out.println(myIntArray[0]);
}
static void intArrayMethod(int[] someIntArray) {
someIntArray = new int[] {1};
}
}
Output:
0
0
myIntArray remains unchanged. intArrayMethod() works on a copy of the myIntArray reference and that copy loses scope at the end of intArrayMethod(). intArrayMethod() is prevented from reassigning the original reference.
Review of Array Contents Argument Behavior
What’s the output of the following?
public class ArrayContentsArgBehavior {
static int[] myIntArray = {0};
public static void main(String[] args) {
System.out.println(myIntArray[0]);
intArrayMethod(myIntArray);
System.out.println(myIntArray[0]);
}
static void intArrayMethod(int[] someIntArray) {
someIntArray[0]++;
}
}
Output:
0
1
In this case the value did change. While the array reference itself cannot be reassigned to point to another array, the elements of the array can be changed.
Varargs Behavior With Primitives and Arrays
So now we go on to the tricky varargs method. What’s the output of the following?
public class VarArgsBehavior {
static int myInt = 0;
static int[] myIntArray = {0};
public static void main(String[] args) {
System.out.println("myInt before: " + myInt);
intVarArgsMethod(myInt);
System.out.println("myInt after: " + myInt);
System.out.println("myIntArray before: " + myIntArray[0]);
intVarArgsMethod(myIntArray);
System.out.println("myIntArray after: " + myIntArray[0]);
}
static void intVarArgsMethod(int... someVarArgsInt) {
someVarArgsInt[0]++;
}
}
Now you see why it’s a little unclear? The first call is using an int. The second call is using an array of ints. In either case though, the intvarArgsMethod() is using array syntax to deal with the arguments. So how does it behave when called with individual primitives? Are they treated like individual primitives or like they were placed into an array before the method call?
The output:
myInt before: 0
myInt after: 0
myIntArray before: 0
myIntArray after: 1
Thankfully, the developers that implemented varargs kept the behavior consistent. Even though vararg parameters are accessed in a manner that makes them look like elements of an array, they are still treated in accordance with how they are declared. Kudos to the varargs implementation team. They could very easily have decided to just handle the argument like an array at all times and that would have resulted in some very odd behavior in special cases.
Conclusion
Bottom line, expect varargs to treat an argument in the manner in which it was declared. If a varargs method is called with an array, its arguments will be treated like an array. If called with one or more individual arguments, they will be treated like individual arguments. Varargs respects the manner in which a method is called.
Joshua Smith
References
Sun Guide to Varargs
SCJP Sun Certified Java Programmer Study Guide