What is Varargs?
Varargs is the feature that allows a method to accept variable number of parameters. The JLS (Java Language Specification) calls it a Variable Arity Method; I guess they are called varargs because that is what they are called in C. However, there are important differences from the way they work in C.
As in C, A variable arity parameter is indicated by an ellipsis following the type name in the formal parameter list, as seen in the printf documentation in javadoc:
PrintStream printf(String format, Object... args)
What this means is that printf method can accept any number of parameters of type Object following the format parameter. If you have any exposure to the printf function in C, you already know that there are constraints on the argument types; you cannot send a String for â€Ã...“%dâ€Â format specification, as follows:
System.out.printf("%d", "Bad");
But that is the contract of the printf method, the Java language does not restrict the type of that parameter. My java compiler happily compiles the above and running it results in java.util.IllegalFormatConversionException.
Read more on "Get to know 'Tiger'"
A lot of C compilers are smart enough to “look into" the format string and verify the argument types against that but it might be some times before javac smartens up to it.
Note the following constraints on declaring a Variable Arity parameter:
- There can be only one variable arity parameter in a method
- It has to be the last parameter of the method
Alternate options
To be sure, it was possible to pass variable number of arguments in previous releases, by creating an array of appropriate type. If a method accepts an array of say String, a call to that method can pass variable number of Strings. Indeed, there are several examples of this in JDK. One popular idiom of creating a list on-the-fly is to use Array.asList():
Arrays.asList(new String<>{"C", "C++", "Java"});
Here the Arrays.asList() method accepts an array of Objects. With the introduction of varargs, the above can be called concisely:
Arrays.asList("C", "C++", "Java");
If you think the vararg is just like an array parameter you are completely correct. Under the hood, that is exactly what it is; the Java compiler converts declarations and calls of varargs to arrays of appropriate type.
The classfile disassembler actually shows Arrays.asList as accepting an array, not varargs. Here is the relevant line from the output of the command â€Ã...“javap java.util.Arrays"
public static java.util.List asList(java.lang.Object<>);
Ok, so the definition of a varargs method looks just like one with array parameter. So what happens to the calls to such a method? Consider the following code
class VarArg3 {
void foo() {
java.util.Arrays.asList("C", "C++", "Java");
}
}
I compiled this to VarArg3.class and decompiled with Jad v 1.5.8g (jad -noctor -nonlb -f VarArg3), which seems to handle Java 5. The result, after reformatting, is as follows:
class VarArg3 {
void foo() {
java.util.Arrays.asList(new java.lang.String<> {"C", "C++", "Java"});
}
}
No surprises. This is exactly what we had in 1.4 , varargs is only a syntactic sugar.
You may notice I have not shown the declaration of Arrays.asList in Java 5. This is because it involves generics. We will revisit this briefly when discussing generics.
One difference between the C and Java versions of varargs is that in C, the parameters can be of different types, while in Java they have to be assignable from one type.
This is not a big issue, because they have to be some objects and can be subclasses of that ancestor type. For example, printf can accept any type. They can even be primitives, but more about them when I cover boxing conversions.
Retrofitting
A few existing methods have been retrofitted to accept variable number of parameters. I already mentioned Arrays.asList. I could not find a ready list of such methods - why would you need it, anyway - but a brute search for ellipsis in javadocs and manual verification of "since" attribute on them reveals the following small list, all in the reflection API:
java.lang.Class
- getConstructor(java.lang.Class...)
- getDeclaredConstructor(java.lang.Class...)
- getDeclaredMethod(java.lang.String, java.lang.Class...)
- getMethod(java.lang.String, java.lang.Class...)
- newInstance(java.lang.Object...)
java.lang.reflect.Method
- invoke(java.lang.Object, java.lang.Object...)
java.lang.reflect.Proxy
- getProxyClass(java.lang.ClassLoader, java.lang.Class...)
The above methods previously existed in JDK. Besides these, some new methods introduced in in JDK 5 are also varargs:
- java.util.Collections.addAll(java.util.Collection, T...). This one should come quite handy, just as Arrays.asList
- java.util.EnumSet.of(E, E...) A generic method that I will talk about when covering enums.
- java.lang.ProcessBuilder.ProcessBuilder(java.lang.String...)
- ProcessBuilder provides more robust facility for executing OS programs then System.exec
java.util.Formatter.format(java.lang.String, java.lang.Object...)
This is perhaps the most interesting. PrintStream.format uses it, which in turn is used by PrintStream.printf. Giving us, yes finally, System.out.printf.
Writing your own
Now that you know how to call a vararg method, let us see how you write your own. Well, the declaration is just as the javadoc above. But how do you access the individual argument values? Here again, the analogy with array type parameter applies. You access them as if it were an array:
void foo(String... args) {
  for (int i = 0; i < args.length; i++) {
    System.out.println(args);
  }
}
Java 5 introduces another method of accessing elements of an collections and arrays; the enhanced for loop. It can be used for varargs as well
void foo(String... args) {
   for (String s: args) {
     System.out.println(s);
  }
 }
Note that this provides sequential, read-only access to the array elements. More on that when we cover the enhanced for loop.
Caveats
I mentioned earlier that vararg parameter is exactly same as an array type parameter. Well, that's not exactly true. The class file format actually distinguishes between the two, and method isVarArgs has been added to class java.lang.reflect.Method to provide reflection support for that:
class IsVarArg {
  static void foo(String... s) {}
  static void bar(Object<> o) {}
  public static final void main(final String... args) {
for (java.lang.reflect.Method method : IsVarArg.class.getDeclaredMethods()) {
System.out.println(method.getName() + " is " + (method.isVarArgs()? "" : "not ") + "a vararg method" );
   }
  }
}
Running the above program produces the following output:
foo is a vararg method
bar is not a vararg method
main is a vararg method
But for all practical purposes they are interchangeable. The only case where it seems to matter is that the actual parameter cannot be a variable length list when the formal parameter is an array while the other way is allowed:
class IsVarArg {
  static void foo(String... args) {}
  static void bar(String<> s) {}
  public static final void main(final String... args) {
       foo("one", "two", "three"); //ok: list for vararg
foo(new String<> {"one", "two", "three"}); // ok: array for vararg
bar(new String<> {"one", "two", "three"}); // ok: array for array
bar("one", "two", "three"); // errorL list for array
  }
}
This is a good thing. An array argument implies that the method can potentially change the contents and for a literal list that would have no meaning.
While varargs are in generally fairly simple to understand, one area that can cause some confusion is the overload resolution. My advice is to not mix array type parameters and varargs methods in the same class, especially not with the same names. And remember the following simple rule: A fixed arity method is preferred over variable arity method.
Another aspect in which varargs differs from array is that there can be only one vararg parameter, there is no way for a method to accept multiple varargs parameters, something that can be done quite easily with array parameters:
foo(String<> s, Integer<>);
In a sense, varargs provide automatic conversion - from a list to an array.
In the next article I will cover another feature of Java 5 that provides automatic conversion - boxing conversions. It provides more conversions then varargs, and has greater potential for abuse and to introduce subtle bugs then varargs. At the same time, it also provides significant benefits.
More on all that in my next article.