3

看这个问题:如何在 Java 8 中动态进行过滤?

问题是在执行过滤器后截断流。我不能使用限制,因为我不知道过滤器之后的列表有多长。那么,我们可以计算过滤后的元素吗?

所以,我想我可以创建一个类来计算并通过地图传递流。代码在这个答案中。

我创建了一个计数但保持元素不变的类,我在这里使用了一个函数,以避免使用我在另一个答案中使用的 lambda:

class DoNothingButCount<T > implements Function<T, T> {
    AtomicInteger i;
    public DoNothingButCount() {
        i = new AtomicInteger(0);
    }
    public T apply(T p) {
        i.incrementAndGet();
        return p;
    }
}

所以我的 Stream 终于:

persons.stream()
    .filter(u -> u.size > 12)
    .filter(u -> u.weitght > 12)
    .map(counter)
    .sorted((p1, p2) -> p1.age - p2.age)
    .collect(Collectors.toList())
    .stream()
    .limit((int) (counter.i.intValue() * 0.5))
    .sorted((p1, p2) -> p2.length - p1.length)
    .limit((int) (counter.i.intValue() * 0.5 * 0.2)).forEach((p) -> System.out.println(p));

但我的问题是关于我的例子的另一部分。

collect(Collectors.toList()).stream().

如果我删除该行,结果是当我尝试执行限制时计数器为零。我通过使用可变对象以某种方式欺骗了“有效最终”的要求。

我可能错了,但我理解流是首先构建的,所以如果我们使用可变对象将参数传递给流中的任何步骤,这些将在创建流时采用。

我的问题是,如果我的假设是正确的,为什么需要这样做?流(如果非并行)可以按顺序通过所有步骤(过滤器、映射..),因此不需要此限制。

4

1 回答 1

8

简短的回答

我的问题是,如果我的假设是正确的,为什么需要这样做?流(如果非并行)可以按顺序通过所有步骤(过滤器、映射..),因此不需要此限制。

如您所知,对于并行流,这听起来很明显:需要此限制,否则结果将是不确定的。

关于非并行流,由于它们当前的设计,这是不可能的:每个项目只被访问一次。如果流确实按照您的建议工作,他们会在进入下一步之前对整个集合执行每一步,我认为这可能会对性能产生影响。我怀疑这就是语言设计者做出这个决定的原因。


为什么它在技术上不起作用collect

你已经知道了,但这里是其他读者的解释。从文档

流是懒惰的;仅在发起终端操作时才对源数据进行计算,并且仅在需要时消耗源元素。

的每个中间操作Stream例如filter()orlimit()实际上只是某种初始化流选项的设置器。

当您调用终端操作forEach()时,例如collect()count(),即发生计算时,将按照先前构建的管道处理项目。

这就是为什么limit()在单个项目通过流的第一步之前评估 的参数。limit()这就是为什么你需要用终端操作结束流,然后用你会知道的开始一个新的。

关于为什么不允许并行流的更详细答案

让您的流管道成为step X > step Y > step Z.

我们希望对我们的物品进行平行处理。因此,如果我们允许步骤 Y 的行为依赖于已经通过 X 的项目,那么 Y 是不确定的。这是因为在一个项目到达步骤 Y 的那一刻,已经通过 X 的项目集在多次执行中不会是相同的(因为线程)。

关于为什么不允许它用于非并行流的更详细的答案

根据定义,流用于处理中的项目。您可以将非并行流视为如下:一个项目经历所有步骤,然后下一个项目经历所有步骤,等等。事实上,文档说明了一切:

流的元素在流的生命周期中只被访问一次。像迭代器一样,必须生成一个新流来重新访问源的相同元素。

如果流不能像这样工作,那么在进入下一步之前只对整个集合执行每个步骤也不会更好。这实际上将允许非并行流中的可变参数,但它可能会对性能产生影响(因为我们会在集合上迭代多次)。无论如何,他们目前的行为不允许你想要什么。

于 2014-04-07T12:46:42.650 回答