Monoxyd
Monoxyd

Reputation: 486

Why are the items in the HashSet always displayed in the same order?

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

Answers (4)

Basil Bourque
Basil Bourque

Reputation: 338516

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

Upvotes: 2

WJS
WJS

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

Federico klez Culloca
Federico klez Culloca

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

Alan
Alan

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

Related Questions