If you ever write a varags method…

November 08, 2010

In some specific cases, methods with a variable argument list might greatly improve the usability of an API. However, it is very easy to overuse or misuse this language feature, leading to cumbersome APIs.

Varags methods shine when the programmer knows how many arguments must be passed to a specific invocation of the method when writing the code or, in other words, when the number of arguments passed to a specific invocation of the method is known at compile time.

As soon as one has to dynamically create the list of arguments, things start to get complicated. As you probably know, variable argument lists are translated by the compiler into an array. Consequently, the dynamically created list of arguments must first be converted to an array before being passed to the method. And as you probably know as well, converting a list to an array in Java sucks.

String[] array = (String[]) list.toArray(new String[0]);

When writing an API, it is difficult to know if someone will have to dynamically generate the list of arguments to your varargs method or not. Hence:

If you ever write a varags method, write a companion method accepting an Iterable.

The easiest implementation is for the varargs method to delegate to the other:

String join(String args…) {
    return join(Arrays.asList(args));
}

String join(Iterable<String> args) {
    ...
}

This will ensure that the user of your API will never have to do the cumbersome conversion from a list to an array.

The other advantage of this companion method is that code having to pass a list of objects whose types are parametrized can use it to avoid the unchecked warnings. Given a factory like the one below, you can get rid of these warnings altogether.

public class GenericLists {

  public static <T> List<T> list(T t) {
    return singletonList(t);
  }

  @SuppressWarnings("unchecked")
  public static <T> List<T> list(T t1, T t2) {
    return asList(t1, t2);
  }

  @SuppressWarnings("unchecked")
  public static <T> List<T> list(T t1, T t2, T t3) {
    return asList(t1, t2, t3);
  }

  ...

}

Indeed, all your varargs methods can be invoked like this without any warnings:

foo(list(Id.<User>of(1), Id.<User>of(2)));

This is extremely handy when writing tests.