[英]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
實現,例如TreeSet
或ConcurrentSkipListSet
。
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()
方法的原因之一——因為位置是不可預測的,所以使用會有問題);
在內部,有默認的capacity
和loadfactor
值(在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.