使用Java 8 Streams API,在调用Collectors.toSet()时可以依赖sorted()吗?

时间:2017-10-20 21:31:53

标签: java collections java-8 java-stream

这是java.util.stream.CollectorstoSet()方法的实现:

public static <T>
Collector<T, ?, Set<T>> toSet() {
    return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add,
                               (left, right) -> { left.addAll(right); return left; },
                               CH_UNORDERED_ID);
}

我们可以看到,它使用HashSet并调用add。来自HashSet documentation,&#34;它不保证集合的迭代顺序;特别是,它不保证订单会随着时间的推移保持不变。&#34;

在以下代码中,List String的{​​{1}}流式传输,排序并收集到Set中:

public static void main(String[] args) {
    Set<String> strings = Arrays.asList("c", "a", "b")
            .stream()
            .sorted()
            .collect(Collectors.toSet());
    System.out.println(strings.getClass());
    System.out.println(strings);
}

这提供了输出:

class java.util.HashSet

[a, b, c]

输出已排序。我认为这里发生的事情是,虽然HashSet文档提供的合同规定订单不是它提供的,但实现恰好按顺序添加。我想这可能会在未来版本中发生变化/在JVM之间有所不同,而更明智的做法是做Collectors.toCollection(TreeSet::new)之类的事情。

致电sorted()时可以依赖Collectors.toSet()吗?

此外,究竟是什么&#34;它不能保证订单在一段时间内保持不变&#34;意思? (我想addremove,调整基础数组的大小?)

2 个答案:

答案 0 :(得分:7)

答案是否定的。将项目添加到集合后,您不能依赖任何订单。来自JDK源代码(HashSet.java):

/**
 * Returns an iterator over the elements in this set.  The elements
 * are returned in no particular order.
 *
 * @return an Iterator over the elements in this set
 * @see ConcurrentModificationException
 */
public Iterator<E> iterator() {
    return map.keySet().iterator();
}

现在,在以前的JDK版本中,即使订单无法保证,您通常也会以的相同顺序获取项目(除非对象的类实现hashCode()并且那么你将获得由hashCode()指示的顺序。对象的创建顺序或对象上hashCode()的调用顺序。正如@Holgar在下面的评论中提到的,在HotSpot中它是后者。你甚至不能指望它,因为这也有例外,因为序列号不是hashCode生成器中的唯一成分。

我最近听到Stuart Marks(负责重写Java 9中大部分集合的人)的讲话,他说他们已经将随机化添加到集合的迭代顺序(在Java 9中由新的集合工厂创建。如果你想听到会话,他谈论的部分开始here - 良好的谈话,强烈推荐的方式!。

因此,即使您曾经依赖于集合的迭代顺序,一旦转移到Java 9,您应该停止这样做。

所有这一切,如果你需要订单,你应该考虑使用SortedSet,  LinkedHashSetTreeSet

答案 1 :(得分:7)

要回答这个问题,您必须了解HashSet的实施方式。顾名思义,HashSet是使用哈希表实现的。基本上,哈希表是由元素哈希索引的数组。散列函数(在Java中,对象的散列由object.hashCode()计算)基本上是一个满足一些条件的函数:

  • (相对)快速计算给定元素
  • 两个.equals()彼此具有相同哈希值的对象
  • 不同项目具有相同哈希值的可能性很小

所以,当你对HashSet进行“排序”(这被理解为“迭代器保留元素的自然顺序”)时,这是由于几个巧合:

  • 元素的自然顺序尊重hashCode s
  • 的自然顺序
  • 哈希表足够小,不会发生冲突(两个元素具有相同的哈希码)

如果查看StringhashCode()方法,您会看到对于单字母字符串,哈希码对应于字母的Unicode索引(代码点) - 因此在此特定例如,只要哈希表足够小,元素就会被排序。然而,这是一个巨大的巧合和

  • 不会保留任何其他排序顺序
  • 不适用于hashCodes不遵循其自然顺序的类
  • 不会保留带冲突的哈希表

此外,这与在流上调用sorted()这一事实无关 - 这仅仅是由于hashCode()的实现方式,因此是哈希表的排序。因此,问题的简单答案是“不”。