簡體   English   中英

為什么 HashSet 中的項總是以相同的順序顯示?

[英]Why are the items in the HashSet always displayed in the same order?

我創建了兩個集合:HashSet 和不可修改的集合。 兩種類型的集合都不能保證元素的順序。 但我注意到,在 hashset 的情況下,結果總是相同的:

 @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
    }
}

有什么有意義的解釋嗎?

Set.of迭代故意洗牌

實際上,在較新版本的 OpenJDK 實現中, Set.of的迭代行為已更改,以在每次使用時任意更改順序。 早期版本確實在連續使用中保持固定的迭代順序。

Set < String > set = Set.of( "J1" , "J2" , "J3" , "J4" );
System.out.println( set );

多次運行該代碼的示例:

[J2、J1、J4、J3]

[J1、J2、J3、J4]

[J2、J1、J4、J3]

這種新的任意改變順序的行為旨在訓練程序員不要依賴任何特定的順序。 這種新行為強化了 Javadoc 所說的內容:沒有特定的順序。

那么為什么HashSet類的迭代順序的行為沒有也改變為改組行為呢? 我可以想象兩個原因:

  • HashSet更老,出現在 Java 2 中。使用該類編寫了數十年的軟件。 據推測,其中一些代碼錯誤地期望某種順序。 現在不必要地改變這種行為會令人討厭。 相比之下, Set.of在其行為發生變化時相對較新且未使用。
  • Set.of可能會隨着 Java 的發展而改變,以在多種實現中進行選擇。 實現的選擇可以取決於被收集的對象的種類,並且可以取決於編譯時或運行時條件。 例如,如果使用Set.of收集枚舉對象,則可以選擇EnumSet類作為返回的底層實現。 這些各種底層實現的迭代順序行為可能會有所不同。 因此,當明天很可能會帶來其他實現時,現在向程序員強調不要依賴今天實現的行為是有道理的。

請注意,我小心地避免使用“隨機化”一詞,而是選擇使用“洗牌”。 這很重要,因為您甚至不應該依賴真正隨機化的Set的迭代順序。 始終認為任何Set對象的迭代都是任意的(並且可能會發生變化)。

NavigableSet / SortedSet可預測迭代順序

如果您想要特定的迭代順序,請使用NavigableSet / SortedSet實現,例如TreeSetConcurrentSkipListSet

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

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

運行時,我們看到這些String對象按字母順序排列。 當我們將每個String對象添加到集合中時, TreeSet類使用它們的自然順序,即使用它們在Comparable接口中定義的compareTo的實現。

navSet = [J1, J2, J3, J4]

順便說一句,如果你想要兩者的最好, TreeSet的排序以及Set.of方便的簡短語法,你可以將它們結合起來。 Set實現(例如TreeSet的構造函數允許您傳遞現有集合。

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

如果要指定排序順序而不是自然順序,請將Comparator傳遞給NavigableSet構造函數。 請參見以下示例,為了簡潔起見,我們在其中使用了 Java 16記錄功能。 我們的Comparator實現基於雇用日期的 getter 方法,因此我們按資歷獲得人員列表。 這是有效的,因為LocalDate類實現了Comparable ,因此有一個compareTo方法。

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

運行時:

navSet.toString() ➠ [Person[name=Carol,whenHired=2014-11-11],Person[name=Alice,whenHired=2019-01-23],Person[name=Bob,whenHired=2021-06-27 ]]

“不保證元素的順序”( 文檔中的實際措辭是“它不保證集合的迭代順序;特別是,它不保證順序會隨着時間的推移保持不變。”)並不意味着“順序是隨機的”。 這意味着“不要依賴順序”。

並且作為推論“不要假設元素不會某種順序排列”。

如果您需要具有可預測迭代順序的Set ,請使用LinkedHashSet

如果您想要(偽)隨機順序,請將其轉換為List並對其進行洗牌,如下所示:

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

在基本情況下,它們可能會以一致的順序在您的系統上顯示,但在其他系統上或在復雜情況下不會顯示。

因此,最好尊重某些行為無法保證的警告。

為什么HashSet中的項總是以相同的順序顯示?

如果您一遍hashSet遍地使用相同的hashSet ,那是因為hashCode每次都用於為相同的一組值以相同的方式構建該集合。 但是沒有保證特定的順序(這是沒有提供帶索引的get()方法的原因之一——因為位置是不可預測的,所以使用會有問題);

在內部,有默認的capacityloadfactor值(在HashSet的 JavaDoc 中進行了解釋),它們會影響給定HashSet的最終順序。 但是這些可以作為參數傳遞給HashSet構造函數。 一個例子如下:

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

印刷

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM