Invoke on varargs
April 30th, 2006The Java5 varargs feature is a nice one, something missing from the C/C++ world and eventually implemented in a much nicer way though with some caveats. I know what I am talking about - I encountered with a colleague a nasty bug in C++ code that was due to a misunderstand in the varargs feature. Eventually we removed all vararg calls in favor of a Java old-style call. This triggered me of course to look into Java’s varargs more deeply, especially when dealing with reflection.
To understand fully the scope let’äs look back to C/C++. In C varargs are used like this:
void myprintf(const char *fmt, …)
{
const char *p;
va_list argp; //declare the list
…
va_start(argp, fmt); // start iteration
while (…) {
i = va_arg(argp, int); // fetch next element as an int
}
va_end(argp); //cleanup
See for the full example and explication Steve Summit’s C Programming Notes Do you see where we determine that the list is exhausted? It must be somewhere in the while-condition. The problem is that you cannot do it without additional information. There is now way to tell wether they are 0, 5 or 500 parameters given. Even it was someone a second problem remains that might defeat this immediatly: va_arg() takes the type to be ready as an argument, so reading a long will take the data you could read with two ints. Type-safety - we have heard of it. Functions like printf() solve this by parsing the format string to find out which parameters have to be read (and how many), alternativly a count is passed (brr) or a 0 is by convention the last parameter (how to pass a zero-value then? In short: A dangerous tool.
Java Varargs are only syntactic sugar: myArgs(String … args) is equivalent to myArgs(String[] args) - try to declare thes methods within a single class and you’ll see the compiler complaining.
The compliance goes that far that these calls are all legal:
v.myArgs(); // passes a new String[]{}
v.myArgs(”d”,”e”,”f”);
v.myArgs(new String[]{”g”,”h”,”i”});
All work because args is a String[]. Should one rely on that? Say, is it really OK to write
void myArgs(String … arg) {
String[] args = args;
This is legal Java, but isn’t the equivalency vararg-array not just an implementation detail one must not rely on?
Difficult to tell from the short notes Sun provides with the JDK, but there are some hints that confirm the hypothesis:
- Many methods from reflect for example had been redeclared to accept varargs where an array had been used before, these methods can still be called from pre-Java5 code.
- The use of reflection on vararg-methods
To fiind the method defined above use v.getClass().getDeclaredMethod(”myArgs”, String[].class); this is the way reflection code can access such a method, so the argument is actually of this type.
A caveat is the invoke, it had been refactored to use varargs, but calling a vararg-method has some surprises left for you
m.invoke(v, “1″,”2″); // wrong number of arguments
m.invoke(v); // again
m.invoke(v, new String[]{”5″,”6″,”7″});
The last one shows a warning in Eclipse:
The argument of type String[] should explicitly be cast to Object[] for the invocation of the varargs method invoke(Object, Object…) from type Method. It could alternatively be cast to Object for a varargs invocation
m.invoke(v, new Object[]{new String[]{”5″,”6″,”7″}}); // this finally works
The last call is a bit quirky, the idea for refactoring the method invoke was that you can invoke foo(a,b) via f.invoke(o,a,b) - this does not work when using varargs if the only arguments are varargs, there you have to go back to the old style. In all other cases the reflection can distinguish varargs properly:
myArgs(String[] a1, String … args)
can be invoked as m.invoke(obj,new String[]{”a1″},new String[]{”5″,”6″,”7″}) (as expected ) but not by m.invoke(obj,new String[]{”a1″},”5″,”6″,”7″) which will throw again wrong number of arguments.
Conclusions
So why is this so much better than the C-style? An array provides the length member so you can tell how many parameters are in the call. Also an array is typed and even if it is just and Object[] you can use reflection on each element individually.
Another thing is if the varargs should be used at all. Frankly, during 10 years C/C++ I implemented a single function accepting varargs, I mostly relied on passing collections. So as varargs in Java are collections they provide a syntactically cleaner alternative to the new Object[]{} clutter.
As reflection code requires the clutter any way should the calls be implemented using arrays? I don’t think so for two reasons:
- The clutter (cosmetic issue, although I met many programmers that were surprised to see that you can instantiate an array the way I did above)
- Future JVMs could exploit the varargs notations for optimizations by assuming that they receive a read-only array
Well, the last one could also be an argument against its usage…
(this article got reposted after being accidentally deleted)
