Java Collectors.groupingBy()---列表是否有序?

31
Collectors.groupingBy() 返回 Map<K,List<T>>,这是否意味着 List<T> 的顺序与流的评估顺序相同?
我没有看到关于列表排序的明确描述,而并发版本明确指出没有排序。如果它没有以某种方式排序,我希望它应该是一个 Collection,除了按接收顺序排列之外,我不知道还有什么其他的排序方式。
我希望保证每个列表中的最后一个值是该组收到的最后一个值。

7
简短回答:如果您有一个有序的数据流(即,源自List、数组、迭代器等),并且您没有使用无序收集器(如并发收集器),那么是的;元素将按照遇到的顺序依次呈现给收集器。请注意,这有时会带来一些成本,如果您不关心这种稳定性保证,那么您可以通过在流中包含unordered()来取消排序。 - Brian Goetz
@Brian Goetz:所以,当我只考虑明确记录为“无序”的收集器时,即使是summarizingInt也不是无序的?这是有原因的吗?那么groupingBy(func, toSet())呢?我知道,由于当前的实现方式,它仍然是有序的,但我们必须永远禁止实现识别顺序独立性的约定吗? - Holger
@Holger Stream API 有着强烈的(有人会说过于强烈)稳定性承诺。请考虑 sorted()distinct() 的顺序保留行为,有些人认为这种行为过度了。因此,丢弃排序是必须记录的,否则您将被困在保留排序的状态中。您可能会问:“为什么不总是记录顺序保留?”但是,请考虑另一种情况:如果 map()toList() 不保留顺序,那就显然是错误的。(例如:strings.stream().map(String::length).collect(toList())。)因此,我们指定允许的偏差。 - Brian Goetz
@Brian Goetz:我明白了。我的剩下的问题是为什么summarizingInt的文档不允许其实现无序。想一想,这一定是一个疏忽,特别是对于summarizingDouble,因为它由DoubleSummaryStatistics支持,而该类明确拒绝排序保证... - Holger
@Holger 一如既往,浮点数算术将无法分类,因为它不是可结合的,这是并行性所需的关键假设。summingDouble() 方法确实有一个注释来说明这一点;summarizingDouble 也应该有。 - Brian Goetz
3个回答

34

groupingBy()的文档中提到:

实现要求:

这会产生类似于以下的结果:

groupingBy(classifier, toList());
根据toList()的文档:

返回:

一个收集所有输入元素到一个List中的Collector,元素顺序按照遇到次序确定。

因此,回答你的问题,只要你的流有定义的遇到次序,就保证得到有序的列表。
编辑:如@Holger指出,为了保持toList()的排序约束,groupingBy()也必须遵守遇到顺序。它的实现说明已经明确暗示了这一点:

实现注意事项:

...如果不需要保留元素呈现给下游收集器的顺序,使用groupingByConcurrent(Function, Collector)可能会提供更好的并行性能。


5

我进行了一项真实测试,我使用以下顺序初始化了一个ArrayList<TimeBased>

{"1", "2019-03-22 10:20:03", "1"},
{"2", "2019-03-22 10:30:03", "2"},
{"2", "2019-03-22 11:20:03", "3"},
{"1", "2019-03-22 11:20:15", "4"},
{"3", "2019-03-22 11:35:03", "5"},
{"2", "2019-03-22 12:20:03", "6"}

尝试对第一列和第二列进行分组,但结果如下:

id  birth                        number
1   Fri Mar 22 10:20:03 CST 2019 1
1   Fri Mar 22 11:20:15 CST 2019 4
2   Fri Mar 22 12:20:03 CST 2019 6
2   Fri Mar 22 11:20:03 CST 2019 3
2   Fri Mar 22 10:30:03 CST 2019 2
3   Fri Mar 22 11:35:03 CST 2019 5

所以,你看到了,顺序是不符合预期的(日期列的顺序混淆)。

在我执行这个操作之后(添加 LinkedList::new):

Map<Integer, Map<Date, List<TimeBased>>> grouped =
                timeBasedBeans.stream().collect(groupingBy(TimeBased::getId, groupingBy(TimeBased::getPeriod,
                        LinkedHashMap::new, toList())));

然后订单就正确了:

id  birth                        number
1   Fri Mar 22 10:20:03 CST 2019 1
1   Fri Mar 22 11:20:15 CST 2019 4
2   Fri Mar 22 10:30:03 CST 2019 2
2   Fri Mar 22 11:20:03 CST 2019 3
2   Fri Mar 22 12:20:03 CST 2019 6
3   Fri Mar 22 11:35:03 CST 2019 5

你的示例将一个Map放入另一个Map中。默认实现使用HashMap,它是无序的。原始问题是关于在使用groupingBy()重载生成列表时列表的顺序。 - Seldon

2

很遗憾,这个保证没有明确说明。

然而,目前产生的Collector并没有UNORDERED特性,因此实际上,产生的List是有序的。

剩下的问题是,由于没有API合同禁止它,未来版本(或另一种实现)是否可以添加该特性并生成无序列表?在实践中,即使有充分的理由,OpenJDK和Oracle也极不愿意引入此类破坏性变化。

在这里,很少有理由进行这样的更改;我认为依赖这种行为是安全的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接