Videos
The set will give much better performance (O(n) vs O(n^2) for the list), and that's normal because set membership (the contains operation) is the very purpose of a set.
Contains for a HashSet is O(1) compared to O(n) for a list, therefore you should never use a list if you often need to run contains.
The ArrayList uses an array for storing the data. The ArrayList.contains will be of O(n) complexity. So essentially searching in array again and again will have O(n^2) complexity.
While HashSet uses hashing mechanism for storing the elements into their respective buckets. The operation of HashSet will be faster for long list of values. It will reach the element in O(1).
Don’t know, don’t care
The concrete class used by Set.of (and List.of, Map.of) is not documented. All we know is that the object returned (a) implements the interface, and (b) is unmodifiable.
That is all we need to know. We should not care about the particular concrete class used under the covers.
Being of unknown concrete class gives freedom to the implementors of the of methods.
- Those programmers are free to optimize according to the nature of your arguments. For example, if you are passing enum arguments, the highly optimized
EnumSetclass might be used behind the scenes. - Those programmers are free to change their concrete class between versions of Java. For example, Java 17 implementation might return one concrete class while Java 18 returns another.
So you should never depend on a particular concrete class being utilized by the of/copyOf methods.
You asked:
What is the real difference between those two?
In your first one, we know the concrete class. And the resulting set is modifiable.
In your second one, we do not know the concrete class, nor do we care about the concrete class. And the resulting set is unmodifiable.
| Code | Concrete Class | Modifiable |
|---|---|---|
| new HashSet<>() {{ add("a"); add("b"); add("c"); }} | known | modifiable |
| Set.of( "a", "b", "c" | unknown | unmodifiable |
Avoid double-brace
As others said, it’s generally best to avoid double-brace initialization.
If you want the convenience of compact literals-style initialization of your modifiable collection, combine with the of methods. You can pass an existing collection to the constructor.
Set< String > set =
new HashSet<>(
Set.of( "a", "b", "c" ) // Pass a collection to constructor.
)
;
“But I do care …”
If you do care about the specific details of the implementation, then you should not use Set.of. The implementation details may vary as discussed above, so you should not rely on a specific implementation with Set.of.
When your project has specific needs, you should explicitly choose a particular implementation that meets those needs. You can choose one of the bundled implementations, obtain one from a third-party library such as Eclipse Collections or Google Guava, or write your own.
Tip: You can leverage the convenient syntax of Set.of by passing its result to the constructor of your chosen implementation. See the code example above:
new HashSet<>( Set.of( … ) ) // Populate new `HashSet` with elements of the unmodifiable `Set` returned from `Set.of`.
The implementation class resulting from Set.of(...) is not guaranteed. It could change depending on the runtime implementation or in future versions. However, some of its characteristics—chiefly immutability—are guaranteed.
When you use "double-brace initialization", you are defining a new anonymous class that derives from the specified type. So MySillyTest$1 extends HashSet because that's what you specified. Note that double-brace initialization has problems; I don't allow it, and I discourage others from using it.
The important difference between the two is the immutability resulting from Set.of(...). If you need a mutable set, it's not an option. But if you can use an immutable set, it provides superior readability and performance.
Even if you need a mutable set, however, don't look at double-brace initialization as an alternative to Set.of(...); just use a HashSet in the conventional way.