我创建了两个集合: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
迭代故意打乱
实际上,Set.of
的迭代行为在最新版本的 OpenJDK 实现中已更改,以便在每次使用时任意更改顺序。早期版本确实在连续使用中保持了固定的迭代顺序。
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
类使用了它们的 natural ordering ,也就是说,使用了 compareTo
的实现在 Comparable
接口(interface)中定义。
navSet = [J1, J2, J3, J4]
顺便说一句,如果您想要两者的优点,即 TreeSet
的排序以及 Set.of
方便的简短语法,您可以将它们组合起来。 Set
实现的构造函数(例如 TreeSet
)允许您传递现有集合。
Set < String > set = new TreeSet <>( Set.of( "J3" , "J1" , "J4" , "J2" ) );
如果您想指定排序顺序而不是自然顺序,请传递 Comparator
到 NavigableSet
构造函数。请参阅以下示例,其中我们使用 records 的 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]]
关于java - 为什么HashSet中的项总是以相同的顺序显示?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68167584/