我正在创建使用takeWhile的片段来探索它的可能性。与flatMap结合使用时,行为与预期不符。请在下面找到代码段。
String[][] strArray = {{"Sample1", "Sample2"}, {"Sample3", "Sample4", "Sample5"}};
Arrays.stream(strArray)
.flatMap(indStream -> Arrays.stream(indStream))
.takeWhile(ele -> !ele.equalsIgnoreCase("Sample4"))
.forEach(ele -> System.out.println(ele));
实际输出:
Sample1
Sample2
Sample3
Sample5
ExpectedOutput:
Sample1
Sample2
Sample3
期望的原因是takeWhile应该执行直到内部条件变为真。我还在flatmap中添加了printout语句以进行调试。流返回两次,符合预期。
但是,如果链中没有flatmap,这样就可以了。
String[] strArraySingle = {"Sample3", "Sample4", "Sample5"};
Arrays.stream(strArraySingle)
.takeWhile(ele -> !ele.equalsIgnoreCase("Sample4"))
.forEach(ele -> System.out.println(ele));
实际输出:
Sample3
此处实际输出与预期输出匹配。
免责声明:这些代码段仅用于代码练习,不提供任何有效的用例。
更新
错误JDK-8193856:修复将作为JDK 10的一部分提供。
更改将更正whileOps
水槽::接受
@Override
public void accept(T t) {
if (take = predicate.test(t)) {
downstream.accept(t);
}
}
更改实施:
@Override
public void accept(T t) {
if (take && (take = predicate.test(t))) {
downstream.accept(t);
}
}
答案 0 :(得分:54)
这是JDK 9中的一个错误 - 来自issue #8193856:
takeWhile
错误地认为上游操作支持并尊重取消,遗憾的是flatMap
并非如此。
如果订购了流,takeWhile
应显示预期的行为。在您的代码中并非完全如此,因为您使用forEach
来放弃订单。如果你关心它,你在这个例子中做了,你应该使用forEachOrdered
。有趣的是:这并没有改变任何事情。
所以也许首先没有订购流? (在这种情况下the behavior is ok。)如果为从strArray
创建的流创建临时变量,并通过在断点处执行表达式((StatefulOp) stream).isOrdered();
来检查它是否被排序,您会发现它确实是有序的:
String[][] strArray = {{"Sample1", "Sample2"}, {"Sample3", "Sample4", "Sample5"}};
Stream<String> stream = Arrays.stream(strArray)
.flatMap(indStream -> Arrays.stream(indStream))
.takeWhile(ele -> !ele.equalsIgnoreCase("Sample4"));
// breakpoint here
System.out.println(stream);
这意味着这很可能是一个实现错误。
正如其他人所怀疑的那样,我现在也认为可能与flatMap
渴望联系。更准确地说,这两个问题可能都有相同的根本原因。
查看WhileOps
的来源,我们可以看到这些方法:
@Override
public void accept(T t) {
if (take = predicate.test(t)) {
downstream.accept(t);
}
}
@Override
public boolean cancellationRequested() {
return !take || downstream.cancellationRequested();
}
takeWhile
使用此代码检查给定的流元素t
是否符合predicate
:
downstream
操作,在本例中为System.out::println
。take
设置为false,因此当下次询问是否应取消管道时(即已完成),它将返回true
。这涵盖了takeWhile
操作。您需要知道的另一件事是forEachOrdered
导致执行方法ReferencePipeline::forEachWithCancel
的终端操作:
@Override
final boolean forEachWithCancel(Spliterator<P_OUT> spliterator, Sink<P_OUT> sink) {
boolean cancelled;
do { } while (
!(cancelled = sink.cancellationRequested())
&& spliterator.tryAdvance(sink));
return cancelled;
}
所有这一切都是:
看起来很有前途,对吧?
flatMap
在好的情况下&#34; (没有flatMap
;您的第二个示例)forEachWithCancel
直接在WhileOp
sink
上运行,您可以看到它是如何发挥作用的:
ReferencePipeline::forEachWithCancel
执行循环:
WhileOps::accept
被赋予每个流元素WhileOps::cancellationRequested
"Sample4"
使谓词失败并且流被取消耶!
flatMap
在&#34;坏情况下&#34; (使用flatMap
;您的第一个示例),forEachWithCancel
对flatMap
操作进行操作,但只需调用forEachRemaining
上的ArraySpliterator
{"Sample3", "Sample4", "Sample5"}
这样做:
if ((a = array).length >= (hi = fence) &&
(i = index) >= 0 && i < (index = hi)) {
do { action.accept((T)a[i]); } while (++i < hi);
}
忽略所有hi
和fence
内容,仅在为并行流拆分数组处理时使用,这是一个简单的for
循环,它将每个元素传递给takeWhile
操作,但从不检查是否已取消。因此,它将热切地穿过那个&#34;子流&#34;中的所有元素。在停止之前,甚至可能through the rest of the stream。
答案 1 :(得分:20)
这个 是一个错误,无论我如何看待它 - 感谢Holger的评论。我不想把这个答案放在这里(严肃地说!),但没有一个答案清楚地说明这是一个错误。
人们说这必须是有序/无序的,这不是真的,因为这会报告true
3次:
Stream<String[]> s1 = Arrays.stream(strArray);
System.out.println(s1.spliterator().hasCharacteristics(Spliterator.ORDERED));
Stream<String> s2 = Arrays.stream(strArray)
.flatMap(indStream -> Arrays.stream(indStream));
System.out.println(s2.spliterator().hasCharacteristics(Spliterator.ORDERED));
Stream<String> s3 = Arrays.stream(strArray)
.flatMap(indStream -> Arrays.stream(indStream))
.takeWhile(ele -> !ele.equalsIgnoreCase("Sample4"));
System.out.println(s3.spliterator().hasCharacteristics(Spliterator.ORDERED));
如果将其更改为:
,这也很有趣String[][] strArray = {
{ "Sample1", "Sample2" },
{ "Sample3", "Sample5", "Sample4" }, // Sample4 is the last one here
{ "Sample7", "Sample8" }
};
然后Sample7
和Sample8
将不会成为输出的一部分,否则他们会。似乎flatmap
忽略将由dropWhile
引入的取消标记。
答案 2 :(得分:11)
如果你看the documentation for takeWhile
:
如果订购此流,则[返回]包含该流的流 从此流中获取的与给定匹配的元素的最长前缀 谓语。
如果此流是无序的,则[返回]由子集组成的流 从此流中获取的与给定谓词匹配的元素。
您的信息流已巧合订购,但takeWhile
不知道。因此,它返回第二个条件 - 子集。您的takeWhile
表现得像filter
。
如果您在takeWhile
之前添加对sorted
的来电,则会看到您期望的结果:
Arrays.stream(strArray)
.flatMap(indStream -> Arrays.stream(indStream))
.sorted()
.takeWhile(ele -> !ele.equalsIgnoreCase("Sample4"))
.forEach(ele -> System.out.println(ele));
答案 3 :(得分:9)
原因是flatMap
操作也是intermediate operations,其中(一个) 有状态短路中间操作 使用takeWhile
。
Holger在this answer中所指出的flatMap
的行为无疑是一个不应错过的参考,以便了解此类短路操作的意外输出。
通过引入终端操作来确定性地使用有序流并对样本执行以下操作,可以通过拆分这两个中间操作来实现预期结果:
List<String> sampleList = Arrays.stream(strArray).flatMap(Arrays::stream).collect(Collectors.toList());
sampleList.stream().takeWhile(ele -> !ele.equalsIgnoreCase("Sample4"))
.forEach(System.out::println);
此外,似乎有一个相关的Bug#JDK-8075939来跟踪已注册的此行为。
编辑 :可以在JDK-8193856接受进一步跟踪此错误。