Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

I'm studying the factory methods for Immutable collections. I see the Set.of() method has 10 varargs overloading (same for Map.of()). I really can't understand why there are so many. In the end the function ImmutableCollections.SetN<>(elements) gets called anyway.

In the documentation I found this:

While this introduces some clutter in the API, it avoids array allocation, initialization, and garbage collection overhead that is incurred by varargs calls.

Is the clutter indeed worth the performance gain? If yes, would that ideally do create a separate method for any N elements?

like image 498
Schidu Luca Avatar asked Aug 02 '17 07:08

Schidu Luca


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.

What is the rule for using varargs in java?

While using the varargs, you must follow some rules otherwise program code won't compile. The rules are as follows: There can be only one variable argument in the method. Variable argument (varargs) must be the last argument.

What is the maximum number of varargs in java?

13) How many maximum numbers of Varargs or Variable-Arguments can be there in a method or a constructor in Java? Explanation: Yes, only one.

How many Varargs can we have in a function?

A method can have only one varargs parameter.


2 Answers

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.

like image 122
Eugene Avatar answered Sep 16 '22 20:09

Eugene


Some aspects of this may be a form of future proofing.

If you evolve an API, you need to pay attention to how the method signature will change, so if we have

public class API {   public static final <T> Set<T> of(T... elements) { ... } } 

We could say that the varargs is good enough... except that the varargs forces the allocation of an object array, which - while reasonably cheap - does actually impact the performance. See for example this microbenchmark which shows a 50% loss of throughput for a no-op logging (i.e. the log level is lower than loggable) when switching to the varargs form.

Ok, so we do some analysis and say the most common case is the singleton, so we decide to refactor...

public class API {   public static final <T> Set<T> of(T first) { ... }   public static final <T> Set<T> of(T first, T... others) { ... } } 

Ooops... that's not binary compatible... it's source compatible, but not binary compatible... to retain binary compatibility we need to keep the previous signature, e.g.

public class API {   public static final <T> Set<T> of(T first) { ... }   @Deprecated public static final <T> Set<T> of(T... elements) { ... }   public static final <T> Set<T> of(T first, T... others) { ... } } 

Ugh... IDE code complete is a mess now... plus how do I create a set of arrays? (probably more relevant if I was using a list) API.of(new Object[0]) is ambiguous... if only we had not added the vararg at the start...

So I think what they did was add enough explicit args to reach the point where the extra stack size meets the cost of vararg creation, which is probably about 10 arguments (at least based on the measurements that Log4J2 did when adding their varargs to the version 2 API)... but you are doing this for evidence based future-proofing...

In other words, we can cheat for all the cases that we do not have evidence requiring a specialized implementation and just fall-through to the vararg variant:

public class API {   private static final <T> Set<T> internalOf(T... elements) { ... }   public static final <T> Set<T> of(T first) { return internalOf(first); }   public static final <T> Set<T> of(T first, T second) { return internalOf(first, second); }   ...   public static final <T> Set<T> of(T t1, T t2, T t3, T t4, T t5, T... rest) { ... } } 

Then we can profile and look at real world usage patterns and if we then see significant usage up to the 4 arg form and benchmarks showing there to be a reasonable perf gain, then at that point, behind the scenes, we change the method impl and everyone gets a win... no recompilation required

like image 36
Stephen Connolly Avatar answered Sep 20 '22 20:09

Stephen Connolly