示例程序:
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()
产生一个顺序流。我只是将结果装箱到Integer
s中,然后Collector
将其收集到中List<Integer>
。很简单
而且,无论我运行此示例程序多少次,都UnsupportedOperationException
不会成功,这意味着永远不会调用我的虚拟组合器。
我有点期望,但是后来我已经误解了流,以至于我不得不问这个问题…
可以将Collector
的组合时,流过被称为 保证 是连续的?
仔细阅读ReduceOps.java中的流实现代码后发现,只有在ReduceTask
完成时才调用Combine函数,并且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()
并通过使组合器抛出异常来对此进行备份。此外,供应商必须被准确地调用一次。无法合并中间结果,因此,如果两次致电供应商,我们就会遇到麻烦。但是由于我们“知道”供应商在顺序模式下仅被调用一次,所以大多数人不必为此担心。实际上,我已经看到人们写“供应商”来违反供应商合同,返回一些现有对象而不是创建一个新对象。
通过使用3-arg形式的collect()
,我们在打破合同的三个函数中有两个。这不应该告诉我们以不同的方式做事吗?
此处的主要工作由累加器功能完成。要完成折叠样式的缩小,我们可以使用严格按从左到右的顺序应用此功能forEachOrdered()
。我们必须在前后进行一些设置和整理代码,但这没问题:
StringBuilder a = new StringBuilder();
list.parallelStream()
.forEachOrdered(b -> a.append(a.toString()).append(b));
System.out.println(a.toString());
自然地,尽管并行运行的性能优势可能会因的订购要求而被否定,但并行运行仍然可以正常工作forEachOrdered()
。
总而言之,如果您发现自己想进行可变的归约但缺少关联的组合器功能,则将您的流限制为顺序执行,将问题 重折叠为左折
运算并forEachRemaining()
在累加器函数上使用。
示例程序: 所以,为了简化这里的问题,没有最终的转换,所以得到的代码非常简单。
在jdk8中,Stream提供带有、和的功能 我在注释中看到了将String Stream转换为String的示例,但我很困惑组合器在函数签名中的用途。我认为在累加器中,新元素已添加到结果大陆? 我尝试给组合器一个null值,但我得到了一个null指针异常。
JavaAPI文档声明方法的参数必须是: 用于组合两个值的关联、非干扰、无状态函数,必须与累加器函数兼容 合路器是双余弦 例如,根据组合顺序,以下示例可能给出不同的结果:或m2。addAll(m1)。 我知道,在这种情况下,我们可以简单地使用方法句柄,例如ArrayList::addAll。然而,在某些情况下,需要Lambda,我们必须按正确的顺序组合项目,否则在并行处理时可能会得到不一致的结果。
问题内容: 在上面的代码中,我可以期望中的名称顺序始终与中的顺序相同吗? 问题答案: 是的,只要您没有将并行流显式转换为模式,即使您正在使用并行流,也可以期望这样做。 顺序永远不会在顺序模式下更改,但可能会在并行模式下更改。流变得无序: 如果您通过调用将其明确转换为无序模式 如果流源报告它是无序的(例如,流是无序的,因为顺序取决于实现,因此您不能依赖它) 如果您使用的是无序终端操作(例如,进行操作
我有两个列表,我想连接成一个列表,然后使用< code>Collectors.toMap来删除基于特定字段的重复项。同一列表内不能有重复,只能在列表之间。 当谈到解决具有相同键的值的冲突时,我希望对第一个列表中的项目进行优先级排序。 我可以提供<代码>(a,b)- 参数是否保证以与列表相同的顺序传递?