【发布时间】:2019-12-12 19:08:58
【问题描述】:
使用 Java 8(如果这很重要),我有一个难以理解的行为。
假设我有一个Entry 类:
static class Entry {
String key;
List<String> values;
public Entry(String key, String... values) {
this.key = key;
this.values = Arrays.asList(values);
}
}
还有一个实例列表:
List<Entry> entries = Arrays.asList(
new Entry("a", "a1"),
new Entry("b", "b1"),
new Entry("a", "a2"));
);
现在我想收集所有具有相同键的条目(并保持不同的值),我偶然发现了“IllegalStateException:流已被操作或关闭”。
生成它的最少代码是:
entries.stream().collect(
Collectors.groupingBy(
e -> e.key,
Collectors.mapping(
e -> e.values.stream(),
Collectors.reducing(Stream.<String>empty(), Stream::concat))
)
);
(我会添加一个collectingAndThen 来满足我的要求,但这不是我的问题的重点)
我看不到代码的哪一部分消耗/作用于流。此外,如果我将代码更改为以下内容,它可以工作:
entries.stream().collect(
Collectors.groupingBy(
e -> e.key,
Collectors.mapping(
e -> e.values.stream(),
Collectors.reducing(Stream::concat))
)
);
我宁愿使用以前的代码,因为后者给我一个Map<K, Optional<V>>,而前者给我一个Map<K, V>。
但问题是:中性元素的使用在减少方面有什么不同,最终导致(至少)其中一个流被消耗?
【问题讨论】:
-
中性元素是指
identity元素吗?它确保您肯定会映射一个T value,并且它不能在Optional<T>中尽可能不存在。根据要求我想收集所有具有相同键(并保持不同值)的条目,您不是在寻找Map<String, Set<String>> map作为输出吗? -
Stream::concat使用传入的流。由于您指定了相同的Stream.empty()作为标识元素,因此这是对 Reduction 的错误使用。事实上,即使没有标识元素,对传入流的隐含消费也是对输入的修改,因此违反了合同。只要没有流实例在流中多次出现,您就可以逃脱。 -
主要问题是你不能有一个流作为标识元素,因为流不能被重用,所以当它试图重用它时,抛出说它被操作或关闭。
-
@Naman 是的,
Map<String, Set<String>>是目标(我知道实现它的几种不同方法),但我在此过程中偶然发现了这个问题。是的,我所说的中立,是指空流。我理解它允许在结果中删除 Optional 的方式和原因,我没想到它会像以前那样失败,因此提出了问题。 -
@GPI 那么你学到了一些非常重要的东西。合同要求
id op x = x始终有效,无论您假设它何时可能被评估。例如,当使用并行流时,它也可能被多次使用,即使在没有分组的情况下使用归约。在这里使用flatMapping是首选,但更好的是在此位置执行您打算对结果流执行的任何操作。
标签: java java-stream