44

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?

ares
  • 4,283
  • 6
  • 32
  • 63
  • 11
    You just answered your own question already - overloading it with 0-10 arguments skips unnecessary array creations. – luk2302 Jan 29 '17 at 08:24
  • @luk2302: Yes, that's I figured out, but I wanted to know if there's any other reason. I guess there's not. – ares Jan 29 '17 at 08:27
  • Okay, then the answer is "no". – luk2302 Jan 29 '17 at 08:28
  • @ares I wish there was another reason. This feels like a micro-optimization to me. – Chetan Kinger Jan 29 '17 at 08:28
  • 1
    @CKing: Yes, since garbage collections nowadays are very optimized and collecting small array shouldn't be such an overhead. But the spec mentions only this reason. – ares Jan 29 '17 at 08:33
  • @ares See my answer for a more functional reasoning. – Chetan Kinger Jan 29 '17 at 08:34
  • 1
    @CKing: The methods are called **Convenience** factory methods as per the JEP. Please don't make unnecessary edits. – ares Jan 29 '17 at 12:19
  • @ares Fair point about convenience methods; however, I wouldn't go that far and say all the edits were *unnecessary*. You said : *Since there is a var args method, what is the point of having extra 11 methods?*. I thought *Why do we have overloaded Collection.of methods with and without varargs* was resonating this sentiment better than *What is the point of overloaded Convenience Factory Methods for Collections in Java 9*. The later sounds like why were all these methods added in the first place. I was trying to make your question more accessible but it's your choice as you're the author. – Chetan Kinger Jan 29 '17 at 12:31
  • I closed this as a duplicate - I hope that this is OK, considering that the core of the question is the same (although one refers to Guava and the other to Java 9). If others concur, it can be reopened. – Marco13 Jan 29 '17 at 15:27
  • 3
    @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". – Chetan Kinger Jan 29 '17 at 15:45
  • @CKing One might consider ["canonicalizing"](http://meta.stackoverflow.com/q/291992/3182664) the answer to the other question: There are dozens of libraries that use this approach of "multiple overloads with different numbers of arguments", and opening a question for **each** of them would not make any sense either (because the *answer* would be the same in all cases!). In this regard, people will still *find* this question when they search for `java 9 collection of`, and find the appropriate answers in the linked question. Let's see whether others share their opinions about this. – Marco13 Jan 29 '17 at 16:02
  • @Marco13 I was simply stating my reason for reopening because you asked if it was OK. I didn't intend to start a discussion here. But to continue, there are subtle implementation details that need to be considered. Does Guava implement these methods the same way as Java does (call the varargs methods always?). Even if it does today, will the two continue to have the same internal implementation in the future? Who will keep track of these internal implementation details and verify whether it impacts the question being asked. It seems like too much work IMO. – Chetan Kinger Jan 29 '17 at 16:10
  • @CKing Sure, no offense, but we can try to sort out whether it's a "duplicate" or not: I think that the accepted answer of the other question is not "great", because it does not cover many points that are mentioned in the current top-answer here. Creating one canonical Q/A: ("Q: What's the point of these multi-overloads?"/"A: These are the reasons: ... ...") might be worthwhile. I think that many of these reasons *are* independent of the underlying implementation, and thus, am not sure whether implementation details should be included in (or only be *relevant for*) such an answer. – Marco13 Jan 29 '17 at 16:16
  • 3
    @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. – Chetan Kinger Jan 29 '17 at 16:30
  • 10
    I am voting to reopen. The [justification](http://mail.openjdk.java.net/pipermail/core-libs-dev/2015-October/035790.html) 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](http://mail.openjdk.java.net/pipermail/core-libs-dev/2015-November/036349.html). – Stefan Zobel Jan 29 '17 at 17:28
  • 3
    @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 Jan 30 '17 at 21:26

6 Answers6

35

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");
Naman
  • 27,789
  • 26
  • 218
  • 353
  • 3
    Isn't this a micro-optimization in an era of ample hardware at your disposal in a high level language such as Java? I so wish there was another reason to it but can't think of one – Chetan Kinger Jan 29 '17 at 08:30
  • In addition to what you quoted, the docs also say *It is not a goal to support high-performance, scalable collections with arbitrary numbers of elements. The focus is on **small collections*** – Chetan Kinger Jan 29 '17 at 15:58
  • 26
    @CKing Yes, this is a micro-optimization. The performance criteria for the JDK libraries are much more stringent than for most applications. These APIs are used in the JDK itself, and they impact startup time, so even apparently tiny optimizations are worth it. – Stuart Marks Jan 30 '17 at 17:21
  • @StaurtMarks Thanks for the comment. So would it be wrong to say that the motivation behind adding these overloads was primarily to do with usage within the JDK and that users of this new API shouldn't bother much about the vararg method unless they care about performance at an extremely miniscule level? I do see some functional advantages of these new methods for the users not interested in the performance aspects over using `Arrays.asList` as mentioned in my answer. What's your take on this? – Chetan Kinger Jan 30 '17 at 18:54
  • 3
    @StuartMarks but then they all (except the two arguments method) delegate to this: `@SafeVarargs @SuppressWarnings("unchecked") SetN(E... input) {` – Eugene Jan 30 '17 at 21:20
  • 6
    @Eugene Yes, that's buried within the implementation, and it can be change compatibly. It just hasn't been fully optimized yet. – Stuart Marks Jan 30 '17 at 22:47
  • 2
    @CKing The APIs are intended for general use. But we wanted to be able to use them within the JDK without sacrificing performance. Regarding general usage of the new APIs, it generally isn't necessary for source code to know or care whether it's calling a fixed-arg or varargs method. If you have `Set.of(a1, a2, ... a10)` and you add another arg, it ends up switching from the fixed to the varargs call, but otherwise it behaves exactly the same. So, just go ahead and use the new APIs. – Stuart Marks Jan 30 '17 at 22:53
  • 1
    @CKing And yes, the varargs method accepts a variable number of arguments, or you can pass it an array, if you have an array that you want copied into a new instance of a collection. – Stuart Marks Jan 30 '17 at 22:56
  • 1
    This overload stuff makes no sense to me and just looks like clutter.. If anyone is looking for performance of a degree such as this, why in hell would they choose Java?? I can't see how these overloads even help at all.. Maybe I'm missing something? :S – Brandon Oct 01 '17 at 16:29
  • Isn't there a way to optimize performance of varargs in compile time or runtime to avoid the array creation? Some compiler or JIT magic? Might make the 11 overloaded methods redundant and still provide high performance. – AlikElzin-kilaka Mar 15 '18 at 14:29
  • 1
    @AlikElzin-kilaka there are [some considerations](https://bugs.openjdk.java.net/browse/JDK-8013269) but no significant progress. The problem is that changes that make the initialization (cold) code path faster do not necessarily have the same positive impact on hot code paths. Afaik, benchmarks with some real life scenarios revealed that with the current JVM, only some methods would benefit while others would degrade. So it won’t happen soon… – Holger Oct 28 '20 at 14:16
11

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.

Mureinik
  • 297,002
  • 52
  • 306
  • 350
  • 3
    Isn't this a micro-optimization in an era of ample hardware at your disposal in a high level language such as Java? I so wish there was another reason to it but can't think of one. – Chetan Kinger Jan 29 '17 at 08:26
  • 3
    @CKing Language/library designers need to consider performance of their code not just in code that is run infrequently; but also what happens when someone uses it in a hot loop of a computationally intensive application. In those cases things that could otherwise be dismissed as "pointless micro-optimizations" do have significant performance impacts. – Dan Is Fiddling By Firelight Jan 29 '17 at 15:35
  • 3
    @DanNeely Is that argument valid in this case? How expensive is it to create an array of 10 elements? – Chetan Kinger Jan 29 '17 at 15:38
  • @CKing My interest levels don't rise to the point of writing a benchmark, but the book cited in [@JuBobs answer](http://stackoverflow.com/users/2541573/jubobs) implies that there are cases where it can become significant. My assumption would be that the impact was indirect - from additional garbage collections - rather then due to the direct time spent moving data through a temporary array. – Dan Is Fiddling By Firelight Jan 29 '17 at 16:44
  • @DanNeely All that is understandable but the JEP documentation also says this : *It is not a goal to support high-performance, scalable collections with arbitrary numbers of elements. The focus is on small collections*. It seems a bit contradicting to me to talk about performance and dismiss it at the same time. Yes, it could have a performance advantage; however, I see more functional advantages as explained in my answer. – Chetan Kinger Jan 29 '17 at 16:46
  • @DanNeely: I do agree with the **hot loop** scenario. Languages do need to possible optimization they can. Although insignificant, if done in a long-running loop, it would make a difference. – ares Jan 29 '17 at 17:36
  • 6
    @DanNeely & @ares : Ironically, `hot loops` scenarios were definitely [considered irrelevant](http://mail.openjdk.java.net/pipermail/core-libs-dev/2015-November/036439.html) for this optimization. – Stefan Zobel Jan 29 '17 at 17:44
11

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 [...]

jub0bs
  • 60,866
  • 25
  • 183
  • 186
  • Okay, so the implementation was copied right from the Josh Bolch's text. – ares Jan 29 '17 at 15:42
  • 2
    The most important two words to take a note of : *determined emprically*. Micro-optimizations most definitely require empirical proof. – Chetan Kinger Jan 29 '17 at 15:47
4

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)

Community
  • 1
  • 1
Chetan Kinger
  • 15,069
  • 6
  • 45
  • 82
  • 1
    It's like instead of the compiler creating an array for me, I'm creating one myself. – ares Jan 29 '17 at 08:36
  • @ares Think of it this way. You sometimes talk to code that returns an array. When you want to convert this array into a list, you can directly pass this to the `List.of` method. Also, the `List` and the array won't be in sync as compared to the `Arrays.asList` method so that's another use case for this method. – Chetan Kinger Jan 29 '17 at 09:02
3

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.

J-Alex
  • 6,881
  • 10
  • 46
  • 64
-2

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.

Mohit Tyagi
  • 2,788
  • 4
  • 17
  • 29