Set.of iteration purposely shuffled
Actually, the iteration behavior of Set.of was altered in more recent versions of the OpenJDK implementation to change the order arbitrarily on each usage. Earlier versions did maintain a fixed iteration order across successive usages.
Set < String > set = Set.of( "J1" , "J2" , "J3" , "J4" );
System.out.println( set );
Example running that code multiple times:
[J2, J1, J4, J3]
[J1, J2, J3, J4]
[J2, J1, J4, J3]
This new arbitrarily-changing-order behavior is intended to train programmers not to rely on any particular order. This new behavior reinforces what the Javadoc states: Expect no particular order.
So why wasn’t the behavior of the HashSet class’ iteration order also changed to a shuffling behavior? I can imagine two reasons:
HashSet is much older, arriving in Java 2. Decades of software has been written using that class. Presumably some of that code incorrectly expects a certain ordering. Changing that behavior unnecessarily now would be obnoxious. In contrast, Set.of is relatively quite new and unused at the time of its change in behavior.
Set.of may well be changed as Java evolves, to choose among several implementations. The choice of implementation can depend on the kinds of objects being collected, and can depend on compile-time or runtime conditions. For example, if collecting enum objects with Set.of, the EnumSet class could be chosen as the underlying implementation returned. These various underlying implementations might vary in their iteration-order behavior. So it makes sense to emphasize to programmers now not to rely on the behavior of today's implementations when tomorrow may well bring other implementations.
Notice that I carefully avoided using the word “randomized”, instead choosing to use “shuffled”. This is important, because you should not even depend on the iteration order of your Set being truly randomized. Always consider any Set object’s iteration to be arbitrary (and subject to change).
Predictable iteration order with NavigableSet/SortedSet
If you want a certain iteration order, use a NavigableSet/SortedSet implementation such as TreeSet or ConcurrentSkipListSet.
NavigableSet < String > navSet = new TreeSet <>();
navSet.add( "J3" );
navSet.add( "J1" );
navSet.add( "J4" );
navSet.add( "J2" );
System.out.println( "navSet = " + navSet.toString() );
When run, we see those String object sorted alphabetically. As we added each of our String objects to the set, the TreeSet class used their natural ordering, that is, used their implementation of compareTo defined in the Comparable interface.
navSet = [J1, J2, J3, J4]
By the way, if you want the best of both, the sorting of TreeSet but also the convenient brief syntax of Set.of, you can combine them. The constructor of Set implementations such as TreeSet allow you to pass an existing collection.
Set < String > set = new TreeSet <>( Set.of( "J3" , "J1" , "J4" , "J2" ) );
If you want to specify the sorting order rather than natural order, pass a Comparator to the NavigableSet constructor. See the following example, where we use the Java 16 feature of records for brevity. Our Comparator implementation is based on the getter method for the date hired, so we get a list of people by seniority. This works because the LocalDate class implements Comparable, and so has a compareTo method.
record Person(String name , LocalDate whenHired) {}
Set < Person > navSet = new TreeSet <>(
Comparator.comparing( Person :: whenHired )
);
navSet.addAll(
Set.of(
new Person( "Alice" , LocalDate.of( 2019 , Month.JANUARY , 23 ) ) ,
new Person( "Bob" , LocalDate.of( 2021 , Month.JUNE , 27 ) ) ,
new Person( "Carol" , LocalDate.of( 2014 , Month.NOVEMBER , 11 ) )
)
);
When run:
navSet.toString() ➠ [Person[name=Carol, whenHired=2014-11-11], Person[name=Alice, whenHired=2019-01-23], Person[name=Bob, whenHired=2021-06-27]]