Sei sulla pagina 1di 7

Have you ever found yourself in the following situation?

You need to pass in many instances of the same object type to a method, but you don't know at compile time how many instances there will be. In the past, the only way to handle this situation was to bundle these objects in an array or other collection. However with J2SE 5.0, you now have the added convenience of using variable arity parameters, known less formally as varargs. In this tip, you will learn why it's a good idea to take advantage of varargs as a client of an API. The tip also cautions against introducing varargs to an API that you create, unless it is warranted. In the following program, VarGreeter, the method printGreeting() takes an unspecified number of String objects as input.
class VarGreeter { public static void printGreeting(String... names) { for (String n : names) { System.out.println("Hello " + n + ". "); } } public static void main(String[] args) { printGreeting("Paul", "Sue"); } }

In VarGreeter, the method printGreeting() is invoked with the call printGreeting("Paul","Sue"). If you run VarGreeter, you get the message:
Hello Paul. Hello Sue.

Change the line in VarGreeter that calls printGreeting() to have as many String object arguments as you like. Each name that you pass in will be greeted. The printGreeting() method works its way through whatever collection of strings it is passed, and greets each in turn. What is perhaps most striking is that this works if no string is passed. You can see this by changing main() to the following and rerunning VarGreeter.
public static void main(String[] args) { printGreeting(); }

This time you will have no output because there is no one to greet. This example illustrates an appropriate use of varargs. You have an unspecified number of String objects to process in the same way. At run time a vararg is converted to an array. You can see that the printGreeting() method uses an enhanced for loop to iterate through the array. Using a vararg is different than using an array. The next few examples highlight the similarities and differences. First consider VarGreeter2:

public class VarGreeter2 { public static void printGreeting(String... names) { for (String n : names) { System.out.println("Hello " + n + ". "); } } public static void main(String[] args) { printGreeting(args); } }

The key is that you can pass in an array as the parameter. The args variable is an array of type String. Run this version with the command:
java VarGreeter2 Paul Sue

and you will get the same output as before:


Hello Paul. Hello Sue.

You can experiment with different numbers of command-line arguments. You can even pass in no arguments. Ordinarily, you need a guard clause to make sure that args does not have length zero. When required, you should check for proper input. In this case, there are no such requirements. The printGreeting() method can handle zero or more strings passed as a list of strings or passed as a single array of strings. Now consider VarGreeter3. Here you will see that you can even change the signature of main(). Although there is no reason for doing this, the canonical public static void main(String[] args) can be changed to public static void main(String ... args):
public class VarGreeter3 { public static void printGreeting(String... names) { for (String n : names) { System.out.println("Hello " + n + ". "); } } public static void main(String... args) { printGreeting(args); } }

Run VarGreeter3 with the command:


java VarGreeter3 Paul Sue

and you will get the same output as before:

Hello Paul. Hello Sue.

Using "main(String... args)" makes it easier to invoke main directly from Java. This can be useful for writing unit tests of your main() method. So far, you have not seen any difference between explicitly declaring printGreeting() using varargs and using an array. Now it's time to look at a difference. Change the signature from printGreeting(String ... names) to printGreeting(String[] names). Notice that in VarGreeter4, you can still pass in an array of strings with the call printGreeting(args):
public class VarGreeter4 { public static void printGreeting(String[] names) { for (String n : names) { System.out.println("Hello " + n + ". "); } } public static void main(String... args) { printGreeting(args); } }

You'll still get the same results as before if you run the command:
java VarGreeter4 Paul Sue

The difference is if you try to call printGreeting using something like printGreeting("Paul", "Sue") as shown in VarGreeter5:
public class VarGreeter5 { public static void printGreeting(String[] names) { for (String n : names) { System.out.println("Hello " + n + ". "); } } public static void main(String... args) { printGreeting("Paul", "Sue"); } }

Try compiling VarGreeter5. It won't compile:


javac VarGreeter5.java VarGreeter5.java:10: printGreeting(java.lang.String[]) in VarGreeter5 cannot be applied to (java.lang.String,java.lang.String) printGreeting("Paul", "Sue");

^ 1 error

The lesson is that varargs give you a great deal of flexibility as the client of an API. Warning: The following examples illustrate some of the dangers in introducing varargs to an API. It is at least as important to know when it is inappropriate to use a technique as it is to know when to use it. The GridLayout class has three constructors, one takes four arguments, one takes two arguments, and one takes no arguments. The constructor with two arguments allows a user to specify the number of rows and columns in the resulting grid. The constructor with four arguments also allows a user to specify the size of the horizontal gap between rows and the size of the vertical gap between columns. The two-argument constructor assumes those values to be zero. The no-argument constructor provides defaults for the number of rows and columns. The following program, PreVarGridLayout, is a simplified class with three constructors that are defined in much the same way as for the GridLayout class:
class PreVarGridLayout { PreVarGridLayout() { this(1, 1); } PreVarGridLayout(int rows, int cols) { this(rows, cols, 0, 0); } PreVarGridLayout(int rows, int cols, int hgap, int vgap) { System.out.println("Create a grid with rows = " + rows + " and cols = " + cols + ", the hgap = " + hgap + " and the vgap = " + vgap + "."); } public static void main(String[] args) { System.out.println("Call no arg constructor:"); new PreVarGridLayout(); System.out.println("Call two arg constructor: 2,3"); new PreVarGridLayout(2, 3); System.out.println ("Call four arg constructor: 4,5,6,7"); new PreVarGridLayout(4, 5, 6, 7); } }

In main(), you can see that each constructor is called. If you run PreVarGridLayout, you will get the following output:
Call no arg constructor: Create a grid with rows = 1 and cols = 1, the hgap = 0 and the vgap = 0. Call two arg constructor: 2,3

Create a grid with rows = 2 and cols = 3, the hgap = 0 and the vgap = 0. Call four arg constructor: 4,5,6,7 Create a grid with rows = 4 and cols = 5, the hgap = 6 and the vgap = 7.

You can transform the program to take advantage of varargs, as shown in VarGridLayout:
public class VarGridLayout { VarGridLayout(Integer... size) { Object temp[] = {1,1,0,0}; System.arraycopy(size, 0, temp, 0, size.length); System.out.printf("Create a grid with rows = %d " + "and cols = %d, the hgap = %d and the vgap = " + "%d. \n", temp); } public static void main(String[] args) { System.out.println("Call no arg constructor:"); new VarGridLayout(); System.out.println("Call two arg constructor: 2,3"); new VarGridLayout(2, 3); System.out.println( "Call four arg constructor: 4,5,6,7"); new VarGridLayout(4, 5, 6, 7); } }

There are no changes to main(), so the constructors are called in the same way in VarGridLayout as they were in PreVarGridLayout, that is, before varargs were introduced. The difference is that now there is a single constructor. The constructor takes zero, two, or four arguments as before and prints the same output. Notice that this time printf() is used to write to the output stream. The printf() facility uses varargs. This allows you to pass in temp as the last parameter to printf(). The method treats temp as an array of type Integer. But there's a serious problem in using varargs this way. You can see the problem by changing the last line of the main() method in PreVarGridLayout to:
new PreVarGridLayout(4,5,6,7,8);

and in VarGridLayout to:


new VarGridLayout(4,5,6,7,8);

Recompile PreVarGridLayout:
javac PreVarGridLayout.java PreVarGridLayout.java:23: cannot find symbol symbol : constructor PreVarGridLayout(int,int,int,int,int) location: class PreVarGridLayout new PreVarGridLayout(4,5,6,7,8);

^ 1 error

You get a compiler error because that signature does not match any of the available constructors for PreVarGridLayout. In fact, if you use an IDE with code sense, it should be able to signal before compile time that this will not compile. Now compile VarGridLayout. You will get no warnings at compile time because your constructor can take zero or more Integer objects as parameters. If you run VarGridLayout, you will get an ArrayIndexOutOfBoundsException. You could provide a check that size.length is either zero, two, or four, and throw your own exception otherwise. The point is that this check is left to you, it is not enforced by the compiler. This means that developers using your code will not have support at compile or writing time for making the correct calls. So the lesson is doing not use varargs for this purpose. Another issue in using varargs is knowing which method will be called if there appears to be competing signatures. In the following class, WhichOne, there are three constructors. The first constructor takes a vararg argument, the second takes two int arguments, and the third takes two Integer arguments:
public class WhichOne {

WhichOne(Integer... size) { System.out.println("Var Args version."); } WhichOne(int i, int j) { System.out.println("Version with int args."); } WhichOne(Integer i, Integer j) { System.out.println("Version with Integer args."); } public static void main(String[] args) { System.out.println("Call w/ two arg:2,3"); new WhichOne(2, 3); System.out.println("Call w/ Integer two arg: 2,3"); new WhichOne(new Integer(2), new Integer(3)); } }

Run WhichOne and you will see the following output:


Call w/ Version Call w/ Version two arg:2,3 with int args. Integer two arg: 2,3 with Integer args.

Even with the varargs version of the constructor, the most specifically-matching constructor is called. Eliminate the second and third constructors so that WhichOne looks like the following:
public class WhichOne {

WhichOne(Integer... size) { System.out.println("Var Args version."); } public static void main(String[] args) { System.out.println("Call w/ two arg:2,3"); new WhichOne(2, 3); System.out.println("Call w/ Integer two arg: 2,3"); new WhichOne(new Integer(2), new Integer(3)); } }

Rerun WhichOne. You should see the following output:


Call w/ two arg:2,3 Var Args version. Call w/ Integer two arg: 2,3 Var Args version.

In the absence of the more specific constructors, the varargs version is called by both new WhichOne(2,3) and by new WhichOne(new Integer(2), new Integer(3)). It is important in constructing an API to make clear to the client how to call the desired method. So the lesson here is avoid using varargs on overloaded methods. To reiterate, the advantage of using varargs is that as a client of an API, you are free to call a method using a sequence of instances of an object of the specified type or an array. You have the freedom to make the call using any number of instances of that type. There are also dangers in using varargs. You might want to avoid providing methods that use varargs when it is important to specify the number of entries or to prevent autoboxing.

Potrebbero piacerti anche