示例程序:
public final class CollectorTest { private CollectorTest() { } private static <T> BinaryOperator<T> nope() { return (t, u) -> { throw new UnsupportedOperationException("nope"); }; } public static void main(final String... args) { final Collector<Integer, ?, List<Integer>> c = Collector.of(ArrayList::new, List::add, nope()); IntStream.range(0, 10_000_000).boxed().collect(c); } }
因此,为简化起见,没有最终转换,因此生成的代码非常简单。
现在,IntStream.range()产生一个顺序流。我只是将结果装箱到Integers中,然后Collector将其收集到中List<Integer>。很简单
IntStream.range()
Integer
Collector
List<Integer>
而且,无论我运行此示例程序多少次,都UnsupportedOperationException不会成功,这意味着永远不会调用我的虚拟组合器。
UnsupportedOperationException
我有点期望,但是后来我已经误解了流,以至于我不得不问这个问题…
可以将Collector的组合时,流过被称为 保证 是连续的?
仔细阅读ReduceOps.java中的流实现代码后发现,只有在ReduceTask完成时才调用Combine函数,并且ReduceTask仅在并行评估管道时才使用实例。因此, 在当前实现中, 在评估顺序管道时永远不会调用组合器。
ReduceTask
但是,规范中没有任何东西可以保证这一点。A Collector是一个对其实现有要求的接口,并且顺序流没有授予任何豁免。我个人很难想象为什么顺序管道评估可能需要调用合并器,但是比我想象更多的人可能会发现它的巧妙用法并实现了它。规范允许这样做,即使今天的实现不支持它,您仍然必须考虑它。
这不足为奇。流API的设计中心是在顺序执行的基础上支持并行执行。当然,程序可以观察它是顺序执行还是并行执行。但是API的设计是要支持一种允许的编程风格。
如果您正在编写一个收集器,但发现写一个联合组合器函数是不可能的(不便,困难或困难),导致您想将流限制为顺序执行,这可能意味着您走错了方向。是时候退后一步,考虑以另一种方式解决问题了。
不需要关联组合器功能的常见归约样式操作称为 fold-left 。主要特征是折叠功能严格从左到右应用,一次执行一次。我不知道并行左折的方法。
当人们试图以我们一直在谈论的方式扭曲收藏家时,他们通常会在寻找诸如左折之类的东西。Streams API对此操作没有直接API支持,但是编写起来很容易。例如,假设您要使用此操作来减少字符串列表:重复第一个字符串,然后追加第二个字符串。很容易证明此操作不具有关联性:
List<String> list = Arrays.asList("a", "b", "c", "d", "e"); System.out.println(list.stream() .collect(StringBuilder::new, (a, b) -> a.append(a.toString()).append(b), (a, b) -> a.append(a.toString()).append(b))); // BROKEN -- NOT ASSOCIATIVE
按顺序运行,将产生所需的输出:
aabaabcaabaabcdaabaabcaabaabcde
但是,当并行运行时,它可能会产生以下内容:
aabaabccdde
由于它是按顺序“工作”的,因此我们可以通过调用来强制执行此操作,sequential()并通过使组合器抛出异常来对此进行备份。此外,供应商必须被准确地调用一次。无法合并中间结果,因此,如果两次致电供应商,我们就会遇到麻烦。但是由于我们“知道”供应商在顺序模式下仅被调用一次,所以大多数人不必为此担心。实际上,我已经看到人们写“供应商”来违反供应商合同,返回一些现有对象而不是创建一个新对象。
sequential()
通过使用3-arg形式的collect(),我们在打破合同的三个函数中有两个。这不应该告诉我们以不同的方式做事吗?
collect()
此处的主要工作由累加器功能完成。要完成折叠样式的缩小,我们可以使用严格按从左到右的顺序应用此功能forEachOrdered()。我们必须在前后进行一些设置和整理代码,但这没问题:
forEachOrdered()
StringBuilder a = new StringBuilder(); list.parallelStream() .forEachOrdered(b -> a.append(a.toString()).append(b)); System.out.println(a.toString());
自然地,尽管并行运行的性能优势可能会因的订购要求而被否定,但并行运行仍然可以正常工作forEachOrdered()。
总而言之,如果您发现自己想进行可变的归约但缺少关联的组合器功能,则将您的流限制为顺序执行,将问题 重折叠为左折 运算并forEachRemaining()在累加器函数上使用。
forEachRemaining()