Reputation: 486
I've created two sets: HashSet and unmodifiable set. Both types of sets do not guarantee the order of the elements. But I have noticed that in case of hashset the result is always the same:
@Test
void displaySets() {
Set<String> hashSet = new HashSet<>();
hashSet.add("J1");
hashSet.add("J2");
hashSet.add("J3");
hashSet.add("J4");
for(String el : hashSet) {
System.out.println(el); // always the same order - J1, J2, J3, J4
}
System.out.println("----------------");
Set<String> set = Set.of("J1", "J2", "J3", "J4");
for(String el : set) {
System.out.println(el); // random order
}
}
Is there any meaningful explanation ?
Upvotes: 2
Views: 103
Reputation: 338516
Set.of
iteration purposely shuffledActually, 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).
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]]
Upvotes: 2
Reputation: 40034
Why are the items in the HashSet
always displayed in the same order?
If you mean the same hashSet
over and over again it is because the hashCode
is used to build the set the same way each time for the same set of values. But no particular order is guaranteed (which is one of the reasons no get()
method with an index is provided - since the location is unpredictable it would be of questionable use);
Internally, there are default capacity
and loadfactor
values (which are explained in the JavaDoc for HashSet
) which can affect the ultimate order of a given HashSet
. But these can be passed as arguments to the HashSet
constructor. An example follows:
Set<Integer> set = new HashSet<>();
set.addAll(Set.of(1,3,4,2,10,9,28,5,6));
System.out.println(set);
System.out.println(set);
System.out.println(set);
Set<Integer> set2 = new HashSet<>(2, 3f);
set2.addAll(set);
System.out.println(set2);
Prints
[1, 2, 3, 4, 5, 6, 9, 10, 28]
[1, 2, 3, 4, 5, 6, 9, 10, 28]
[1, 2, 3, 4, 5, 6, 9, 10, 28]
[4, 28, 1, 5, 9, 2, 6, 10, 3]
Upvotes: 0
Reputation: 27119
"Do not guarantee the order of the elements" (the actual wording in the documentation is "It makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time.") doesn't mean "the order is random". It means "don't rely on the ordering".
And as a corollary "don't assume the elements won't be in some order".
If you need a Set
with a predictable iteration order use a LinkedHashSet
.
If you want it in (pseudo)random order, convert it to a List
and shuffle it, like this:
Set<String> hashSet = new HashSet<>();
hashSet.add("J1");
hashSet.add("J2");
hashSet.add("J3");
hashSet.add("J4");
List<String> toList = new ArrayList<>(hashSet);
Collections.shuffle(toList);
Upvotes: 1
Reputation: 732
They might be displayed in a consistent order on your system under basic circumstances, but not another system, or not under complex circumstances.
So it is best to respect the warnings that some behaviour is not guaranteed.
Upvotes: 0