java - 为什么HashSet中的项总是以相同的顺序显示?

标签 java set 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 迭代故意打乱

实际上,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实现如 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 类使用了它们的 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" ) );

如果您想指定排序顺序而不是自然顺序,请传递 ComparatorNavigableSet 构造函数。请参阅以下示例,其中我们使用 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/

相关文章:

java - 排序 HashMap

java - bazel 远程工作人员可部署 jar 不工作

java - Activity 泄漏了最初添加到此处的窗口(当登录数据错误时)

java - HashSet 代替 ArrayList 在性能上存在缺陷

caching - Redis 缓存新闻文章

java - 使用集合时验证重复的用户条目

java - 尝试对本地主机服务器进行压力测试时,JMeter 出现错误

python - pprint 排序字典而不是集合?

javascript - 如何制作懒惰集?

java - 如何在 HashMap/HashSet 中存储两个相等的字符串或任何对象(具有不同的引用)?