Skip to main content

Java Tech: Using Variable Arguments

April 19, 2004

{cs.r.title}








Contents
Basic Syntax and Uses
Tiger Uses Variable Arguments
   Reflection
   Process Building
   Formatting
C-style printf() and scanf()
Conclusion

Welcome to Java Tech. This new column explores all kinds of Java technology in
terms of theory and practicality. For example, one article might investigate
Tiger's generics language feature, whereas another article might apply Java to
the construction of a web shopping cart application or a "smart" computer game
that can beat the pants off many game players. I hope you enjoy the adventure.

Speaking of Tiger (also known as J2SE 5.0), this article focuses on the new variable arguments language
feature. After examining basic syntax, we'll cover diverse uses for variable
arguments.

Note: This column is based on J2SE 5. Sun has established an external
version number of 5.0 and an internal version number of
1.5.0 to this release of the Java 2 platform.

Basic Syntax and Uses

Tiger's variable arguments language feature makes it possible to call a method
with a variable number of arguments. Before making that call, the rightmost
parameter in the method's parameter list must conform to the following syntax:

type ... variableName

The ellipsis (...) identifies a variable number of arguments, and
is demonstrated in the following summation method.

static int sum (int ... numbers)
{
   int total = 0;
   for (int i = 0; i < numbers.length; i++)
        total += numbers [i];
   return total;
}

Call the summation method with as many comma-delimited integer arguments as
you desire -- within the JVM's limits. Some examples: sum (10, 20) and
sum (18, 20, 305, 4).

Look closely at the method declaration above, and you'll discover that variable
arguments is implemented in terms of arrays. (numbers.length and
numbers [i] are the giveaways.) It turns out that the ellipsis is
just syntactic sugar for having the compiler create and initialize an array of
same-typed values and pass that array's reference to a method. For example,
the compiler converts the method call sum (10, 20) into an equivalent
sum (new int [] {10, 20}) method call.

Can you change int ... numbers to int [] numbers and
still invoke sum (10, 20)? Answer: no. If you try to do that, the
compiler reports an error. Use sum (new int [] {10, 20}) instead.

Summing a list of integers is one basic use for variable arguments. Computing
the average of a list of floating-point numbers, concatenating a list of
strings into a single string, and finding the maximum/minimum values in lists
of floating-point numbers are other basic uses. The following
BasicUses.java source code demonstrates these basic uses, to give
you more exposure to variable arguments.

// BasicUses.java
class BasicUses
{
   public static void main (String [] args)
   {
      System.out.println ("Average: " +
                          avg (20.3, 3.1415, 32.3));
      System.out.println ("Concatenation: " +
                          concat ("Hello", " ", "World"));
      System.out.println ("Maximum: " +
                          max (30, 22.3, -9.3, 173.2));
      System.out.println ("Minimum: " +
                          min (30, 22.3, -9.3, 173.2));
      System.out.println ("Sum: " +
                          sum (20, 30));
   }
   static double avg (double ... numbers)
   {
      double total = 0;
      for (int i = 0; i < numbers.length; i++)
           total += numbers [i];
      return total / numbers.length;
   }
   static String concat (String ... strings)
   {
      StringBuilder sb = new StringBuilder ();
      for (int i = 0; i < strings.length; i++)
           sb.append (strings [i]);
      return sb.toString ();
   }
   static double max (double ... numbers)
   {
      double maximum = Double.MIN_VALUE;
      for (int i = 0; i < numbers.length; i++)
           if (numbers [i] > maximum)
               maximum = numbers [i];
      return maximum;
   }
   static double min (double ... numbers)
   {
      double minimum = Double.MAX_VALUE;
      for (int i = 0; i < numbers.length; i++)
           if (numbers [i] < minimum)
               minimum = numbers [i];
      return minimum;
   }
   static int sum (int ... numbers)
   {
      int total = 0;
      for (int i = 0; i < numbers.length; i++)
           total += numbers [i];
      return total;
   }
}

Look closely at BasicUses.java and you'll discover a second Tiger
innovation: java.lang.StringBuilder. I use that class, instead of
java.lang.StringBuffer, in the concatenation method because
StringBuilder's lack of synchronization offers better performance
than StringBuffer. (Why call a synchronized method and obtain the
resulting performance hit, no matter how small, when it's not necessary?)

Tiger Uses Variable Arguments

As you've just seen, variable arguments are useful in simple methods. But they
are even more useful in the Tiger SDK. That SDK uses variable arguments in the
areas of reflection, process building, and formatting (and probably other areas that I have yet to discover.)

Reflection
java.lang.Class and a few java.lang.reflect classes
(such as Method and Constructor) reveal methods that
employ the new ... variable arguments syntax. Examples include
Class's
public Method getMethod(String name, Class ...
parameterTypes)
and Method's
public Object
invoke(Object obj, Object ... args)
. Those methods are callable, either
in the traditional manner or in the new variable arguments manner. Consider an
example where a classfile represents the class below:

class SomeClass
{
   public void someMethod (String strArg, int intArg)
   {
      System.out.println ("strArg = " + strArg);
      System.out.println ("intArg = " + intArg);
   }
}

Now suppose you want to dynamically load the SomeClass classfile
and reflectively invoke its solitary method. That task can be accomplished, in
the traditional manner, with the code fragment below:

Class [] argTypes =
{
   String.class,
   int.class
};

/* Note: Java 1.5's autoboxing language feature lets
  you replace "new Integer (20)" with "20". The
  compiler converts "20" to "new Integer (20)".
  This code fragment assumes an earlier version
  of Java -- which is why "new Integer (20)" is
  specified instead of "20".
*/
Object [] methodData =
{
   "A",
   new Integer (20)
};
Class c = Class.forName ("SomeClass");
Method m = c.getMethod ("someMethod", argTypes);
m.invoke (c.newInstance (), methodData);

In contrast, variable arguments let you express the code fragment above more
compactly, as follows:

Class c = Class.forName ("SomeClass");
Method m = c.getMethod ("someMethod", String.class, int.class);
m.invoke (c.newInstance (), "A", 20);

You save keystrokes and the code is much easier to read.

Process Building

The new java.lang.ProcessBuilder class makes it possible to build
operating system processes and manage a collection of process attributes (the
command, an environment, a working directory, and whether or not the error
stream is redirected). One of that class's constructors and the

public
ProcessBuilder command(String ... command)
method use the new variable
arguments syntax to simplify the specification of a command for execution and
its arguments. For example, to execute Windows' notepad program,
and have that program load the autoexec.bat batch file from the
root directory (on the current drive), specify the code fragment below:

Process p = new ProcessBuilder ("notepad", "\\autoexec.bat").start ();

The constructor specifies the command to execute, along with a variable number
of arguments to pass to the command. The start() method call
starts the command executing and returns a java.lang.Process
reference for retrieving process-exit status (in addition to other tasks).

Without variable arguments, you would probably specify the above code fragment
in the following manner:

String [] commandAndArgs =
{
   "notepad",
   "\\autoexec.bat"
};
Process p = new ProcessBuilder (commandAndArgs).start ();

Once again, variable arguments makes the code easier to read.

Formatting

Possibly the major reason for including variable arguments (and autoboxing) in
Tiger is the new java.util.Formatter class. In combination with
its java.lang.Appendable helper interface, Formatter
formats data values of arbitrary data types and outputs the formatted results
to arbitrary destinations.

Unless your application has specialized needs, you won't need to work directly
with Formatter and Appendable. Instead, you'll work
with one of the format methods found in the following classes:
java.io.PrintStream, java.io.PrintWriter, and
java.lang.String. Behind the scenes, those classes work with the
Formatter class (and sometimes with Appendable, too).

PrintStream, PrintWriter, and String
each present two format methods. One method takes a
java.util.Locale argument, where the other method does not. The
return types vary, as well. PrintStream's format
methods return PrintStream references, PrintWriter's
format methods return PrintWriter references, and
String's format methods return String
references. And finally, String's format methods are
static methods, whereas the format methods in the other two
classes are instance methods. The following code fragment demonstrates two of these
format methods:

String s = String.format ("%.2f", 1.256);
System.out.format ("%05d", 1234);
String's locale-less format method reveals a pattern
argument, consisting of %.2f ("format a floating-point value such
that there are exactly two digits after the decimal point, and employ rounding
if necessary"), followed by the floating-point value to format:
1.256. The formatted result -- 1.26 -- returns as a
String, whose reference assigns to s.

Similarly, PrintStream's locale-less format method
takes a pattern followed by a single value. However, the %05d
pattern states that an integer is to be formatted to a minimum of five digits. If
there are not enough digits, leading zeroes display to the left of the non-zero
digits. Instead of returning the result -- 01234 -- for storage,
PrintStream's format outputs 01234 to a
stream (the standard output stream, in this case).

Note: java.text.MessageFormat also reveals a format
method with a variable number of arguments. Unlike the previously discussed
format methods, this format method is not a wrapper
for Formatter.

C-style printf() and scanf()

Coming from a C background, I equate variable arguments with what are probably
the most popular functions in C's standard library: printf() and
scanf(). (It's a bit silly, but either function pops into my mind
when someone mentions variable arguments.) For the uninitiated, C developers
create formatted output by calling printf(), and obtain formatted
input by calling scanf().

My fondness for those functions resulted in considerable pleasure when I found
a pair of printf methods in each of the PrintStream
and PrintWriter classes. It doesn't matter that they exist as
convenience methods delegating to equivalent format methods. It's
still nice to have official Java versions of printf().

Official versions of scanf() don't exist: scanf()
relies upon pointers, which Java doesn't support. However, Tiger introduces a
new java.util.Scanner class that makes up for the lack of
scanf(). This class lets you create a simple regular expression-
oriented text scanner, for scanning primitive types and strings from an input
source.

The following FormattedIO.java source code demonstrates one of the
printf methods and Scanner performing the equivalent
of some commented-out C code.

// FormattedIO.java
import java.util.Scanner;
class FormattedIO
{
   public static void main (String [] args)
   {
      /*
         // Execute the equivalent of the following
         // C code fragment.
         int i;
         float f;
         char s [50];
         printf ("Enter three values -- integer float string: ");
         scanf ("%d %f %s", &i, &f, s);
         printf ("%d %f %s\n", i, f, s);
      */
      System.out.printf ("Enter three values -- " +
                        integer float string: ");
      Scanner sc = new Scanner (System.in);
      int i = sc.nextInt ();
      float f = sc.nextFloat ();
      String s = sc.nextLine ();
      System.out.printf ("%d %f %s\n", i, f, s);
   }
}

After prompting the user to enter three values, FormattedIO
creates a scanner that scans characters from standard input. The program next
calls the methods nextInt(), nextFloat(), and
nextString() to extract an integer, followed by a floating-point
value, followed by a string of characters -- up to, but not including the line
terminator -- from the standard input stream. Finally,
System.out.printf ("%d %f %s\n", i, f, s); formats those values
into a sequence of characters that it sends to the standard output.

Conclusion

Variable arguments are a subtle but quite useful feature in Tiger's arsenal of
new language features. Uses range from simple methods to reflection, process
building, and formatting. Perhaps the versatility of variable arguments comes
out best in the new printf methods. Although scanf
methods that use variable arguments (in the C sense) are not possible, Tiger's
inclusion of a Scanner class makes up for that deficiency.

You really didn't think I'd let you get away without doing some homework, did
you? I have a couple of exercises for you to work on. Answers will appear next
month:

  1. Is void foo (String ... args, int x) { } legal Java code? Why
    or why not?

  2. Create a PrintFDemo application that demonstrates many of the
    formatting options made available by Formatter.

Next month, Java Tech enters the realm of zero sum perfect information games,
such as chess.

Jeff Friesen is a freelance software developer and educator specializing in Java technology. Check out his site at javajeff.mb.ca.
Related Topics >> Programming   |   

Comments

hi Jeff, When we see the decompiled code for vargs, the ...

hi Jeff,
When we see the decompiled code for vargs, the concept looks so easy to digest but your example code is very good for understanding too.