What Is the Point of Overloaded Convenience Factory Methods for Collections in Java 9

What is the point of overloaded Convenience Factory Methods for Collections in Java 9

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");

Specific Collection type returned by Convenience Factory Method in Java 9

The class returned by List.of is one of the package-private static classes and therefore not part of the public API:

package java.util;
...

class ImmutableCollections {
...

// Java 9-10
static final class List0<E> extends AbstractImmutableList<E> {
...
}

static final class List1<E> extends AbstractImmutableList<E> {
...
}

static final class List2<E> extends AbstractImmutableList<E> {
...
}

static final class ListN<E> extends AbstractImmutableList<E> {
...
}

// Java 11+
static final class List12<E> extends AbstractImmutableList<E> {
...
}

static final class ListN<E> extends AbstractImmutableList<E> {
...
}
}

So this is not an ArrayList (neither a LinkedList). The only things you need to know is that it is immutable and satisfies the List interface contract.

Java 9 collections' convenience factory methods as an alternative to collection literals

I've crafted a simple JMH benchmark:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class Temp {

private Object value;

@Setup
public void setUp() {
value = 50;
}

@Benchmark
public boolean list1() {
return List.of("one").contains(value);
}

@Benchmark
public boolean list2() {
return List.of("one", "two").contains(value);
}

@Benchmark
public boolean list3() {
return List.of("one", "two", "three").contains(value);
}

@Benchmark
public boolean list4() {
return List.of("one", "two", "three", "four").contains(value);
}

@Benchmark
public boolean set1() {
return Set.of("one").contains(value);
}

@Benchmark
public boolean set2() {
return Set.of("one", "two").contains(value);
}

@Benchmark
public boolean set3() {
return Set.of("one", "two", "three").contains(value);
}

@Benchmark
public boolean set4() {
return Set.of("one", "two", "three", "four").contains(value);
}
}

After running the benchmark with -prof gc, I can make the following conclusion: JIT optimizes list1, list2, set1, set2, but not list3, list4, set3, set4 [1]

This seems totally reasonable because for N >= 3 listN/setN create more complex List/Set implementations than for N <= 2.

List implementation for 2 elements:

static final class List2<E> extends AbstractImmutableList<E> {
private final E e0;
private final E e1;
...
}

List implementation for 3 or more elements:

static final class ListN<E> extends AbstractImmutableList<E> {
private final E[] elements;
...
}

ListN contains another level of indirection (an array) which apparently makes escape analysis much harder.


JMH output (slightly changed to fit the page):

Benchmark                  Mode  Cnt     Score      Error   Units
list1 avgt 5 3,075 ? 1,165 ns/op
list1:·gc.alloc.rate avgt 5 0,131 ? 1,117 MB/sec
list1:·gc.alloc.rate.norm avgt 5 ? 10?? B/op
list1:·gc.count avgt 5 ? 0 counts

list2 avgt 5 3,161 ? 0,543 ns/op
list2:·gc.alloc.rate avgt 5 0,494 ? 3,065 MB/sec
list2:·gc.alloc.rate.norm avgt 5 0,001 ? 0,003 B/op
list2:·gc.count avgt 5 ? 0 counts

list3 avgt 5 33,094 ? 4,402 ns/op
list3:·gc.alloc.rate avgt 5 6316,970 ? 750,240 MB/sec
list3:·gc.alloc.rate.norm avgt 5 64,016 ? 0,089 B/op
list3:·gc.count avgt 5 169,000 counts
list3:·gc.time avgt 5 154,000 ms

list4 avgt 5 32,718 ? 3,657 ns/op
list4:·gc.alloc.rate avgt 5 6403,487 ? 729,235 MB/sec
list4:·gc.alloc.rate.norm avgt 5 64,004 ? 0,017 B/op
list4:·gc.count avgt 5 165,000 counts
list4:·gc.time avgt 5 146,000 ms

set1 avgt 5 3,218 ? 0,822 ns/op
set1:·gc.alloc.rate avgt 5 0,237 ? 1,973 MB/sec
set1:·gc.alloc.rate.norm avgt 5 ? 10?? B/op
set1:·gc.count avgt 5 ? 0 counts

set2 avgt 5 7,087 ? 2,029 ns/op
set2:·gc.alloc.rate avgt 5 0,647 ? 4,755 MB/sec
set2:·gc.alloc.rate.norm avgt 5 0,001 ? 0,010 B/op
set2:·gc.count avgt 5 ? 0 counts

set3 avgt 5 88,460 ? 16,834 ns/op
set3:·gc.alloc.rate avgt 5 3565,506 ? 687,900 MB/sec
set3:·gc.alloc.rate.norm avgt 5 96,000 ? 0,001 B/op
set3:·gc.count avgt 5 143,000 counts
set3:·gc.time avgt 5 108,000 ms

set4 avgt 5 118,652 ? 41,035 ns/op
set4:·gc.alloc.rate avgt 5 2887,359 ? 920,180 MB/sec
set4:·gc.alloc.rate.norm avgt 5 104,000 ? 0,001 B/op
set4:·gc.count avgt 5 136,000 counts
set4:·gc.time avgt 5 94,000 ms

[1] Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)

Why java.util.Set has arbitrary methods of?

If you pass a single argument, static <E> Set<E> of(E e1) will be executed, since methods with varargs have a lower priority during method overloading resolution (i.e. they are only considered by the compiler if no methods without varargs are applicable).

The 11 of methods were created for performance reasons. They are more efficient than the varargs method, since the varargs method requires instantiation of an array.

Java 9, Set.of() and Map.of() varargs overloads

At the moment that method is called anyway - this could change. For example it could be that it creates a Set with only three elements, 4 and so on.

Also not all of them delegate to SetN - the ones that have zero, one and two elements have actual classes of ImmutableCollections.Set0, ImmutableCollections.Set1 and ImmutableCollections.Set2

Or you can read the actual question regarding this ... here Read the comments from Stuart Marks in that question -as he is the person that created these Collections.

Why does EnumSet have many overloaded of methods?

Varargs methods create an array.

public static void foo(Object... args) {
System.out.println(args.length);
}

This works, because of the implicit array creation. EnumSet is a class designed to be very, very fast, so by creating all the extra overloads they can skip the array creation step in the first few cases. This is especially true since in many cases Enum don't have that many elements, and if they do, the EnumSet might not contain all of them.

Javadoc for EnumSet<E> of(E e1, E e2, E e3, E e4, E e5):

Creates an enum set initially containing the specified elements. Overloadings of this method exist to initialize an enum set with one through five elements. A sixth overloading is provided that uses the varargs feature. This overloading may be used to create an enum set initially containing an arbitrary number of elements, but is likely to run slower than the overloadings that do not use varargs.

Multiple static factory methods on java util.List interface

I have found an answer in an article of Java World which seems pretty logical when you think about it.

In each method list, the first method creates an empty unmodifiable collection. The next 10 methods create unmodifiable collections with up to 10 elements. Despite their API clutter, these methods avoid the array allocation, initialization, and garbage collection overhead incurred by the final varargs method, which supports arbitrary-sized collections.

Why does Guava's ImmutableList have so many overloaded of() methods?

Varargs and generics do not play nicely together. Varargs methods can cause a warning with generic arguments, and the overloads prevent that warning except in the rare case that you want to add more than 11 items to the immutable list using of().

The comments in the source say:

These go up to eleven. After that, you just get the varargs form, and whatever warnings might come along with it. :(

Note that Java 7's @SafeVarargs annotation was added specifically to eliminate the need for this sort of thing. A single of(E...) method annotated with @SafeVarargs could be used and would not give warnings with generic arguments.



Related Topics



Leave a reply



Submit