McQueeney.com
 

Tom's Blog

Using JDK 5 varargs as bulk setter method
Published by Tom | August 18, 2004 12:45 AM EDT |
After thinking I couldn't see using the new varargs feature in JDK 5 every day, I thought of a potential use that could cut down on repetitive code: initializing a bean or value object.

As an example, say I wanted to create Person class that contains the person's first name, middle name, last name, and a nickname. I want all values to default to an empty string if not supplied by the caller.

For a standard Java bean, I could create a class with all the "name" instance variables initialized to the empty string, then write setters for the values. That way, Person bean instances would have the values set by callers, but contain empty strings for any values unset. For convenience, I'd probably include a bulk setter method that would take all four strings. Something like:
   setAllNames(String last, String first, String middle, String nick) {
      lastName = last;
      firstName = first;
      middleName = middle;
      nickName = nick;
   }
But maybe the caller would want to just set the first name and last name. Of course, they could call the method passing in empty parameters:
   setAllNames("McQueeney", "Tom", "", "");
but for convenience -- especially if there were more the two unneeded parameters, I'd probably want to provide the method:
   setLastFirst(String last, String first) {
      lastName = last;
      firstName = first;
   }
And now I can see where having a method that would take a variable number of arguments could help.

As a test, I wrote the following sample Person class. Instead of providing all the setters and getters and creating a bulk setter, as mentioned above, I put all property-setting in the constructor for two reasons: first, to keep the code short, and second, to show that a varargs constructor becomes the default constructor. This seems like a neat little feature. (While in the back of my mind I'm thinking, "How are programmers going to abuse this neat feature?")

Here's my Person class, coded using JDK 5.
public class Person {
    private String lastName;
    private String firstName;
    private String middleName;
    private String nickName;

    public Person(String... params) {
        lastName = firstName = middleName = nickName = "";

        if (params.length > 0) lastName = params[0];
        if (params.length > 1) firstName = params[1];
        if (params.length > 2) middleName = params[2];
        if (params.length > 3) nickName = params[3];

        if (params.length > 4) {
            throw new IllegalArgumentException(
                "Constructor called with too many arguments"
            );
        }
    }

    public String toString() {
        return "last: '" + lastName + "', first: '" + firstName +
            "', middle: '" + middleName + "', nick: '" + nickName + "'";
    }

    public static void main(String[] args) {
        System.out.println(
            "No args: " +
            new Person()
        );
        System.out.println(
            "1 arg: " +
            new Person("McQueeney")
        );
        System.out.println(
            "2 arg: " +
            new Person("McQueeney", "Tom")
        );
        System.out.println(
            "3 arg: " +
            new Person("McQueeney", "Tom", "X")
        );
        System.out.println(
            "4 arg: " +
            new Person("McQueeney", "Tom", "X", "Spiff")
        );
        System.out.println(
            "5 arg: " +
            new Person("McQueeney", "Tom", "X", "Spiff", "bad!")
        );
    }
}
(I'm not recommending the above coding style as a best-practice!) Here's the output:
> java -cp . com.mcqueeney.tiger.Person
No args: last: '', first: '', middle: '', nick: ''
1 arg: last: 'McQueeney', first: '', middle: '', nick: ''
2 arg: last: 'McQueeney', first: 'Tom', middle: '', nick: ''
3 arg: last: 'McQueeney', first: 'Tom', middle: 'X', nick: ''
4 arg: last: 'McQueeney', first: 'Tom', middle: 'X', nick: 'Spiff'
Exception in thread "main" java.lang.IllegalArgumentException:
  Constructor called with too many arguments
     at com.mcqueeney.tiger.Person.(Person.java:19)
     at com.mcqueeney.tiger.Person.main(Person.java:51)
The benefit of varargs now is I can provide a bulk property setter with only one method, and allow the caller to pick how many of the properties to set.

Of course, bulk setters like this suffer from the same problems as other bulk setters:
  • You can't name the parameters, so you have to read the javadocs to figure out what order to pass everything.
  • You still have to provide the missing values for properties in the "middle" of the varargs set that you really don't want to set.
(By the way, I think the Groovy language allows you to specify the parameter names when you call a method.)

I can see bulk setters using varargs reducing some code. For instance, the java.util.GregorianCalendar has constructors that takes initial values for the date and time:
    public GregorianCalendar(int year, int month, int dayOfMonth);
    public GregorianCalendar(int year, int month, int dayOfMonth,
                             int hourOfDay, int minute);
    public GregorianCalendar(int year, int month, int dayOfMonth,
                             int hourOfDay, int minute, int second);
which seem like they could be implemented with one Integer varargs constructor, using the new auto-unboxing feature in JDK 5 to easily set the integer properties. (Out of curiosity, I checked the source code for JDK beta 3 Build 60, and, no, they didn't retrofit GregorianCalendar this way.)

When searching around the Internet for code samples using varargs, one of the more-complete explanations was this Chapter 5 excerpt from Java 1.5 Tiger: A Developer's Notebook, by Brett McLaughlin and David Flanagan.
20040818 Wednesday August 18, 2004 Permalink Comments [7]
Comments:

I think you are very wrong... varargs should be used for System.out.printf(...), for X.query("select * from Table where x=? and date<?", 3, now), or X.setProperties(person, "firstName", "Tom", "lastName", "Mcq", "ssn", 123456789, ...).

for you example, first of all I won't have one method to set them all. If I do, I won't use varargs, all params should be provided, even if null: setAllNames("J", "Yu", null, null).

Posted by J Yu on August 18, 2004 at 03:22 AM EDT
Website: http://jroller.com/page/javay #

Why not just have String[] as a single parameter and then call the method with new String[] {"firstName", "lastName"} ... it's basically what the compiler does, but it's much clearer if *you* do it.

Posted by Gabriel Mihalache on August 18, 2004 at 03:35 AM EDT
Website: http://www.individualism.ro/ #

I agree 101% with J Yu. I think varargs are well suited for things like printf etc. I cringe at the idea of "bulk setters" being implemented that way and more generally varags being used where traditional method overloading really should be done. That said, I bet we see a whole lot of that kind of code written anyway.

With JDK 5, we have a whole new arsenal of new guns that we may use to shoot ourselves in our collective feet. Take care not to do that.

Posted by Jeff Brown on August 18, 2004 at 08:52 AM EDT
Website: http://www.jeffandbetsy.net/b2/ #

Thank you for the great comments on how easily varargs can be abused. I think the biggest risk and problem with using varargs in a bulk setter is: (1) the API goes out the window, since it now becomes more difficult to tell the caller how many and of what type of parameters to pass, which could lead to mistakes, and (2) illegal use of the API has to be caught at runtime instead of at compile time. I think this is what J Yu and Jeff are saying, and I agree completely. Varargs gives us more power, which can be abused. The System.out.printf() using varargs, and JDBC statements using varargs will face the same problem. But any time the API calls out for an array of flexible size in order to vary what parameters are passed, varargs can make the API cleaner. Either way, you can't catch misuse of the API until runtime. Using varargs, though, can make coding the client classes easier.

Posted by Tom McQueeney on August 18, 2004 at 12:37 PM EDT
Website: http://mcqueeney.com/ #

Named and default arguments are wonderful (Oracle PL/SQL has them, and I wish Java did)

FUNCTION setAllNames(
first_name varchar2 := null,
middle_initial varchar2 := null,
last_name varchar2 := )
IS
BEGIN
...
END;


Invocation :
setAllNames(first_name => 'Jason', last_name => 'Vogel');
setAllNames(last_name => 'Vogel',first_name => 'Jason');

This construct is clean, efficient and means that you have a lot less to overload. You don't end up with

setAllNames(string first_name, string last_name)
setAllNames(string first_name, string middle_initial, string last_name)
setAllNames(string first_name, string middle_initial, string last_name, string nick_name)

You just have the last one with defaults... [of Null]

Jason Vogel

Posted by Jason Vogel on August 25, 2004 at 10:37 AM EDT #

It's funny that you say "How are programmers going to abuse this neat feature?". Because I think that what you did is a great example of how programmers are going to abuse the feature! Hetrogeneous parameters (uh, parameters that are different) should be explicitly named as parameters. Otherwise, what do you have to go on to figure out what they are - the javadocs (you hope)? Have ever tried to read a constructor as it is now (new Something(true, 15, false, false), for example). It's hard enough to read.


I feel confident saying that varargs should only be used when all the variable arguments are the same "kind" of parameter. For example, the var args in printf are all the same "kind" of argument - they are all formatting information. Likewise, they could add a constructor to ArrayList that had a varargs parameter, so you could say new ArrayList(1, 2, 3, 4).


But if your parameters are different "kinds" of parameters, it's a nightmare to use varargs. Like new GregorianCalendar(1990, 5, 5, 5, 5, 5) isn't already a nightmare to read!

Posted by Paul Rivers on October 01, 2004 at 05:29 PM EDT #

"ocusing on the benefits of PHP"

like not having to deal with stupid modularization issues when all of your thousands of functions share the same namespace and are directly accessible anytime? or perhaps the ease of declaring global variables sprinkled across any one of your thousands source files?

PHP is pure junk. Python and Ruby are way better. Heck, even Perl ( from whence PHP came ) is way better, despite the baroque ( but very useful ) syntax...

Posted by John Doe on May 17, 2005 at 12:50 PM EDT #

Comments are closed for this entry.