31
votes

Java 9 comes with convenience factory methods for creating immutable lists. Finally a list creation is as simple as:

List<String> list = List.of("foo", "bar");

But there are 12 overloaded versions of this method, 11 with 0 to 10 elements, and one with var args.

static <E> List<E>  of(E... elements)

Same is the case with Set and Map.

Since there is a var args method, what is the point of having extra 11 methods?

What I think is that var-args create an array, so the other 11 methods can skip creation of an extra object and in most cases 0 - 10 elements will do. Is there any other reason for this?

6
You just answered your own question already - overloading it with 0-10 arguments skips unnecessary array creations.luk2302
@Marco13 I am voting to reopen based on the idea that there are plenty of questions out there that transcend technologies. Also voting to reopen because given that these features are now available in java, people would search for "java 9 collection.of" more than "Guava colleciton.of".CKing
@Marco13 I believe creating a canonical question and pointing these two questions to it as duplicates would make sense. Until we do that, I feel this question should remain open. I wouldn't want to hog on the comment space of this question for this discussion so let's see what others say.CKing
I am voting to reopen. The justification for similar overloads in other APIs (Guava, EnumSet) was different - they provided the overloads because @SafeVarargs did not exist at the time whereas the driver for the overloads in JEP 269 was performance.Stefan Zobel
@StefanZobel agree; especially since the general discussion point is that this is a micro-optimization to exclude the array creation and at the same time all methods (except the 2 parameters) delegate to this: @SafeVarargs @SuppressWarnings("unchecked") SetN(E... input) {Eugene

6 Answers

29
votes

From the JEP docs itself -

Description -

These will include varargs overloads, so that there is no fixed limit on the collection size. However, the collection instances so created may be tuned for smaller sizes. Special-case APIs (fixed-argument overloads) for up to ten of elements will be provided. While this introduces some clutter in the API, it avoids array allocation, initialization, and garbage collection overhead that is incurred by varargs calls. Significantly, the source code of the call site is the same regardless of whether a fixed-arg or varargs overload is called.


Edit - To add motivation and as already mentioned in the comments by @CKing too :

Non-Goals -

It is not a goal to support high-performance, scalable collections with arbitrary numbers of elements. The focus is on small collections.

Motivation -

Creating a small, unmodifiable collection (say, a set) involves constructing it, storing it in a local variable, and invoking add() on it several times, and then wrapping it.

Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a", "b", "c")));

The Java 8 Stream API can be used to construct small collections, by combining stream factory methods and collectors.

// Java 8
Set<String> set1 = Collections.unmodifiableSet(Stream.of("a", "b", "c").collect(Collectors.toSet()));

Much of the benefit of collection literals can be gained by providing library APIs for creating small collection instances, at significantly reduced cost and risk compared to changing the language. For example, the code to create a small Set instance might look like this:

// Java 9 
Set set2 = Set.of("a", "b", "c");
10
votes

As you suspected, this is a performance enhancement. Vararg methods create an array "under the hood", and having method which take 1-10 arguments directly avoids this redundant array creation.

8
votes

You may find the following passage of item 42 of Josh Bloch's Effective Java (2nd ed.) enlightening:

Every invocation of a varargs method causes an array allocation and initialization. If you have determined empirically that you can’t afford this cost but you need the flexibility of varargs, there is a pattern that lets you have your cake and eat it too. Suppose you’ve determined that 95 percent of the calls to a method have three or fewer parameters. Then declare five overloadings of the method, one each with zero through three ordinary parameters, and a single varargs method for use when the number of arguments exceeds three [...]

4
votes

You can also look at it the other way around. Since varargs methods can accept arrays, such a method would serve as an alternative means to convert an array to a List.

String []strArr = new String[]{"1","2"};
List<String> list = List.of(strArr);

The alternative to this approach is to use Arrays.asList but any changes made to the List in this case would reflect in the array which is not the case with List.of. You can therefore use List.of when you don't want the List and the array to be in sync.

Note The justification given in the spec seems like a micro-optimzation to me. (This has now been confirmed by the owner of the API himself in the comments to another answer)

3
votes

This pattern is used for optimization of methods which accept varargs parameters.

If you can figure out that the most time you're using only couple of them, you probably would like to define a method overloadings with the amount of most used parameters:

public void foo(int num1);
public void foo(int num1, int num2);
public void foo(int num1, int num2, int num3);
public void foo(int... nums);

This will help you to avoid array creation while calling varargs method. The pattern used for performance optimization:

List<String> list = List.of("foo", "bar");
// Delegates call here
static <E> List<E> of(E e1, E e2) { 
    return new ImmutableCollections.List2<>(e1, e2); // Constructor with 2 parameters, varargs avoided!
}

More interesting thing behind this is that starting from 3 parameters we are delegating to varargs constructor again:

static <E> List<E> of(E e1, E e2, E e3) { 
    return new ImmutableCollections.ListN<>(e1, e2, e3); // varargs constructor
}

This seems strange for now, but as I may guess - this is reserved for future improvements and as an option, potential overloading of all constructors List3(3 params), List7(7 params)... and etc.

-2
votes

As per Java doc: The collections returned by the convenience factory methods are more space efficient than their mutable equivalents.

Before Java 9:

Set<String> set = new HashSet<>(3);   // 3 buckets

set.add("Hello");
set.add("World");
set = Collections.unmodifiableSet(set);

In above implementation of Set, there are 6 objects are creating : the unmodifiable wrapper; the HashSet, which contains a HashMap; the table of buckets (an array); and two Node instances (one for each element). If a VM take 12-byte per object then there are 72 bytes are consuming as overhead, plus 28*2 = 56 bytes for 2 elements. Here the large amount is consumed by overhead as compared to the data stored in collection. But in Java 9 this overhead is very less.

After Java 9:

Set<String> set = Set.of("Hello", "World");

In above implementation of Set, only one object is creating and this will take very less space to hold data due to minimal overhead.