这是java.util.stream.Collectors
类toSet()
方法的实现:
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;意思? (我想add
,remove
,调整基础数组的大小?)
答案 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
,
LinkedHashSet
或TreeSet
答案 1 :(得分:7)
要回答这个问题,您必须了解HashSet
的实施方式。顾名思义,HashSet
是使用哈希表实现的。基本上,哈希表是由元素哈希索引的数组。散列函数(在Java中,对象的散列由object.hashCode()
计算)基本上是一个满足一些条件的函数:
.equals()
彼此具有相同哈希值的对象所以,当你对HashSet
进行“排序”(这被理解为“迭代器保留元素的自然顺序”)时,这是由于几个巧合:
hashCode
s 如果查看String
类hashCode()
方法,您会看到对于单字母字符串,哈希码对应于字母的Unicode索引(代码点) - 因此在此特定例如,只要哈希表足够小,元素就会被排序。然而,这是一个巨大的巧合和
此外,这与在流上调用sorted()
这一事实无关 - 这仅仅是由于hashCode()
的实现方式,因此是哈希表的排序。因此,问题的简单答案是“不”。