Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java method params: var args vs array

Throughout the Google Guava library, I have noticed the tendency to use the "one (or two) plus var args" technique.

Examples:

  • void add(T value, T... moreValueArr)
  • void add(T value, T value2, T... moreValueArr)

It took me a while to figure out why: To prevent call with zero args (in first case) or one arg (in second case).

Expanding further on this technique, if given the choice between scenarios A and B below, which is preferable? I am hoping someone with deep Java knowledge can provide insight.

Scenario A: (two methods)

  1. void add(T... valueArr)
  2. void add(Iterable<? extends T> iterable)

Scenario B: (three methods)

  1. void add(T value, T... moreValueArr)
  2. void add(T[] valueArr)
  3. void add(Iterable<? extends T> iterable)

One idea why B might be better: I have noticed many Java programmers are not aware that arrays can be passed directly as var args. Thus, B might provide a hint about what is possible.

Finally, I realize B has additional development, testing, and maintenance overhead. Please leave those considerations aside.

This question is a subtle variation on my original question: Java varags method param list vs. array

like image 442
kevinarpe Avatar asked Aug 09 '14 10:08

kevinarpe


People also ask

Is it good to use varargs in Java?

Varargs are useful for any method that needs to deal with an indeterminate number of objects. One good example is String. format . The format string can accept any number of parameters, so you need a mechanism to pass in any number of objects.

Is Varargs an array?

As mentioned earlier, varargs are arrays so we need to work with them just like we'd work with a normal array.

What is var ARG method in Java?

Variable Arguments (Varargs) in Java is a method that takes a variable number of arguments. Variable Arguments in Java simplifies the creation of methods that need to take a variable number of arguments.

Can I pass array to Varargs?

Passing an ArrayList to method expecting vararg as parameter To do this we need to convert our ArrayList to an Array and then pass it to method expecting vararg. We can do this in single line i.e. * elements passed. // function accepting varargs.


2 Answers

Conclusion will be drawn at the end. Skip to the end if you just want the conclusion.


The primary goal is performance.

If there are lots of use cases where there is only 1 or 2 elements that would get passed, you can avoid the creation of an array. Yes, there is still a zero-length array that will be passed, but since a zero-length array cannot be modified, the JVM is allowed to pass a shared instance for example which if cached has no performance impact at all.

The most shining example for this is the EnumSet.of() methods (which returns an EnumSet of the listed enum instances).

You'll see the following overloads:

static <E extends Enum<E>> EnumSet<E> of(E e);
static <E extends Enum<E>> EnumSet<E> of(E first, E... rest);
static <E extends Enum<E>> EnumSet<E> of(E e1, E e2);
static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3);
static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4);
static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4, E e5);

If you call the of() method with 5 or less elements, there will be no array created because there are overloads that can take 5 or less elements. Performance is also mentioned in the javadoc of EnumSet.of(E first, E... rest)

This factory, whose parameter list uses the varargs feature, may be used to create an enum set initially containing an arbitrary number of elements, but it is likely to run slower than the overloadings that do not use varargs.

As to why to use of(E first, E... rest) even if there is a separate of(E e1, E e2):

This is simply a convenience for the implementation. If you declare a first parameter, you can use it without having to check the array's length or having to use indexing. You can use to check its type for example (which is often important when working with generics).

It doesn't really force to pass at least one argument because you can just as easily pass a null as you could pass an empty array if there would only be the vararg parameter.

Vararg vs array

If the type of the array is not a primitive type then there is no real difference, except the array parameter forces you to explicitly create the array while the vararg parameter allows you to either pass an array or just list the elements.

Despite the historical reasons for array parameter (varargs only joined Java with 5.0) there are still places when arrays have to be used:

  1. If the array parameter is an "outgoing" parameter meaning the method receiving the array whishes to fill the array. Example: InputStream.read(byte[] b)
  2. If there are other parameters, possibly multiple arrays, obviously vararg is not enough as there can be only one vararg parameter (which also must be at the end).

Iterable parameter

Iterable in this case is an alternative to pass multiple values to the method but it is not a replacement for array and vararg parameters. Iterable is for collections, as arrays or listing the elements cannot be used if the parameter is Iterable (arrays don't implement Iterable). If the caller has the input data in the form of a collection (e.g. List or Set), the Iterable parameter is the most convenient, the most general and the most efficient way to pass the elements.


Drawing a conclusion

Back to your original Scenario A and B. Since both of your scenarios contain the method with the Iterable parameter and since Iterables do not "mix" with arrays, I reduce the question by omitting those:

Scenario A: (one method)

  1. void add(T... valueArr)

Scenario B: (two methods)

  1. void add(T value, T... moreValueArr)
  2. void add(T[] valueArr)

Since the method add() only reads from the array (and not writes to it; assumed from the name add), every use case can be solved with both of them. I would go with Scenario A for simplicity.

like image 67
icza Avatar answered Sep 19 '22 00:09

icza


There's no particular reason the choices you offer as A and B are mutually exclusive, nor the only options available. Instead, recognize that varargs solve a different problem than collections.

Guava (and modern Java best practices) highly encourage using collections over arrays. They are much more extensible and interchangeable than arrays, and offer more powerful abstractions, such as lazy iteration.

On the other hand, varargs provide a nice way to invoke methods. If you expect people to want to pass in arguments directly, rather than as part of a collection, varargs is more convenient. Just about all varargs style methods should simply call your collection equivalent methods, and do as little else as possible, however, because that convenience becomes inconvenience as soon as you have to actually work with the arrays. There is very little value in creating new methods that take arrays as arguments directly.

But why the weird vararg signatures?

There's a few reasons you want or need the type of signatures you're seeing Guava use a lot:

  1. To enforce stricter signatures:

    You can call a vararg method with any number of arguments, including zero. If your method doesn't make any sense without any parameters, a signature of void add(T one, T ... rest) enforces that requirement at the type level.

  2. To avoid generics conflicts:

    Sometimes you'll want to define a varargs method that, due to type erasure, is identical to another method. For instance, if you defined void add(T ... arr) as well as void add(Iterable<T> iter) with a generic enough type, it's possible passing an iterable would actually match the varargs method. Using void add(T one, T ... arr) helps keep these methods distinct for the compiler. (I'll try to find a more concrete example of this issue).

  3. To avoid object creation:

    Sometimes you'll see method signatures that seem like they could be varargs, but aren't, such as the overloads of ImmutableList.of(). The purpose of these methods is to avoid the extra array allocation that must happen behind the scenes in a varargs call. This is really more of a case of leaky abstractions than anything else, and functionally it's safe to ignore. Unless you're implementing a method that you can expect to be called as often as things like Guava utilities, the allocation savings are probably not worth the added code complexity.

In conclusion, stick to writing methods that take Iterable, Iterator, or an appropriate Collection sub-interface. If you anticipate users of your methods wanting to pass in individual values, consider adding varargs methods that wrap the arguments into collections internally, as a convenience for the caller.

like image 26
dimo414 Avatar answered Sep 21 '22 00:09

dimo414