Stworzyłem dwa zestawy: HashSet i zestaw niemodyfikowalny. Oba rodzaje zestawów nie gwarantują kolejności elementów. Ale zauważyłem, że w przypadku hashset wynik jest zawsze taki sam: @Test ...

2
Monoxyd 28 czerwiec 2021, 20:17

3 odpowiedzi

Najlepsza odpowiedź

Set.of iteracja celowo przetasowana

Właściwie zachowanie iteracji Set.of zostało zmienione w nowszych wersjach implementacji OpenJDK, aby dowolnie zmieniać kolejność przy każdym użyciu. Wcześniejsze wersje utrzymywały stałą kolejność iteracji w kolejnych zastosowaniach.

To nowe zachowanie polegające na arbitralnej zmianie kolejności ma na celu wytrenowanie programistów, aby nie polegali na żadnym określonym porządku. To nowe zachowanie wzmacnia to, co stwierdza dokument Javadoc: Nie oczekuj żadnego szczególnego porządku.

Dlaczego więc zachowanie kolejności iteracji klasy HashSet nie zmieniło się również na zachowanie tasowania? Mogę sobie wyobrazić dwa powody:

  • HashSet jest znacznie starszy, pojawia się w Javie 2. Dziesięciolecia oprogramowania zostały napisane przy użyciu tej klasy. Przypuszczalnie część tego kodu błędnie oczekuje określonej kolejności. Niepotrzebna zmiana tego zachowania teraz byłaby nieprzyjemna. Natomiast Set.of jest stosunkowo nowy i nieużywany w momencie zmiany zachowania.
  • Set.of może być zmieniany wraz z rozwojem Javy, do wyboru spośród kilku implementacji. Wybór implementacji może zależeć od rodzaju gromadzonych obiektów i może zależeć od warunków czasu kompilacji lub czasu wykonywania. Na przykład, jeśli zbierasz obiekty wyliczenia za pomocą Set.of, EnumSet można wybrać jako zwróconą implementację bazową. Te różne podstawowe implementacje mogą różnić się zachowaniem kolejności iteracji. Dlatego warto podkreślić, że teraz programiści nie polegają na zachowaniu dzisiejszych implementacji, kiedy jutro mogą przynosić inne implementacje.

Zauważ, że ostrożnie unikałem używania słowa „losowo”, zamiast tego wybrałem „tasowanie”. Jest to ważne, ponieważ nie powinieneś nawet polegać na kolejności iteracji twojego Set, które są naprawdę losowe. Zawsze uważaj dowolną iterację obiektu Set za dowolną (i podlegającą zmianom).

Przewidywalna kolejność iteracji z NavigableSet/SortedSet

Jeśli chcesz uzyskać określoną kolejność iteracji, użyj NavigableSet/SortedSet implementacja, taka jak TreeSet lub ConcurrentSkipListSet.

NavigableSet < String > navSet = new TreeSet <>();
navSet.add( "J3" );
navSet.add( "J1" );
navSet.add( "J4" );
navSet.add( "J2" );

System.out.println( "navSet = " + navSet.toString() );

Po uruchomieniu widzimy te obiekty String posortowane alfabetycznie. Gdy dodaliśmy każdy z naszych String obiektów do zestawu, klasa TreeSet użyła ich naturalne porządkowanie, czyli wykorzystano ich implementację compareTo zdefiniowano w interfejsie Comparable.

navSet = [J1, J2, J3, J4]

Przy okazji, jeśli chcesz najlepszego z obu, sortowania TreeSet, ale także wygodnej krótkiej składni Set.of, możesz je połączyć. Konstruktor implementacji Set, takich jak TreeSet, umożliwia przekazanie istniejącej kolekcji.

Set < String > set = new TreeSet <>( Set.of( "J3" , "J1" , "J4" , "J2" ) );

Jeśli chcesz określić porządek sortowania, a nie porządek naturalny, przekaż Comparator do konstruktora NavigableSet. Zobacz poniższy przykład, w którym używamy funkcji rekordów w języku Java 16, aby uzyskać zwięzłość. Nasza implementacja Comparator opiera się na metodzie pobierania dla daty zatrudnienia, więc otrzymujemy listę osób według stażu pracy. Działa to, ponieważ klasa LocalDate implementuje Comparable, podobnie jak compareTo metoda.

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 ) )
        )
);

Podczas biegu:

navSet.toString() ➠ [Osoba[imię=Karol, whenZatrudniony=2014-11-11], Osoba[imię=Alicja, whenZatrudniony=2019-01-23], Osoba[imię=Bob, whenZatrudniony=2021-06-27 ]]

2
Basil Bourque 28 czerwiec 2021, 21:13

Mogą być wyświetlane w spójnej kolejności w systemie w podstawowych okolicznościach, ale nie w innym systemie lub nie w złożonych okolicznościach.

Dlatego najlepiej jest przestrzegać ostrzeżeń, że pewne zachowanie nie jest gwarantowane.

0
Alan 28 czerwiec 2021, 17:26

„Nie gwarantuj kolejności elementów” (rzeczywiste sformułowanie w dokumentacja to "Nie daje żadnych gwarancji co do kolejności iteracji zestawu; w szczególności nie gwarantuje, że kolejność pozostanie stała w czasie." nie oznacza "kolejność jest losowa". Oznacza to „nie polegaj na zamawianiu”.

I jako następstwo „nie zakładaj, że elementy nie będą w jakiejś kolejności”.

Jeśli potrzebujesz Set z przewidywalną kolejnością iteracji, użyj LinkedHashSet.

Jeśli chcesz go w (pseudo)losowej kolejności, przekonwertuj go na List i przetasuj, w ten sposób:

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);
1
Federico klez Culloca 28 czerwiec 2021, 17:26