将元素从Stream添加到现有列表的更好方法是什么?

时间:2016-09-14 16:22:35

标签: java collections java-8 java-stream

我必须编写一些代码,将Java 8 Stream的内容多次添加到List中,并且我无法弄清楚最佳方法是什么。根据我在SO上阅读的内容(主要是这个问题:How to add elements of a Java8 stream into an existing List)和其他地方,我将其缩小到以下几个选项:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Accumulator<S, T> {


    private final Function<S, T> transformation;
    private final List<T> internalList = new ArrayList<T>();

    public Accumulator(Function<S, T> transformation) {
        this.transformation = transformation;
    }

    public void option1(List<S> newBatch) {
        internalList.addAll(newBatch.stream().map(transformation).collect(Collectors.toList()));
    }

    public void option2(List<S> newBatch) {
        newBatch.stream().map(transformation).forEach(internalList::add);
    }
}

这个想法是对Accumulator的同一个实例多次调用这些方法。选择是在使用中间列表和在流外部调用Collection.addAll()之间,还是从流中为每个元素调用collection.add()

我倾向于选择更符合函数式编程精神的选项2,并避免创建中间列表,但是,调用addAll()而不是调用add() n次可能会有好处当n很大时。

两种选择中的一种明显优于另一种吗?

编辑:JB Nizet有一个非常酷的answer,它会延迟转换,直到所有批次都被添加。在我的情况下,需要直接执行转换。

PS:在我的示例代码中,我使用transformation作为占位符,用于需要在流上执行的任何操作

2 个答案:

答案 0 :(得分:6)

最好的解决方案是第三个,完全避开内部列表。让流为您创建最终列表:

假设您有一个List<List<S>>,其中包含您必须应用相同转换的N个批次,您可以

List<T> result = 
    batches.stream()
           .flatMap(batch -> batch.stream())
           .map(transformation)
           .collect(Collectors.toList());

答案 1 :(得分:4)

首先,您的第二个变体应该是:

public void option2(List<S> newBatch) {
  newBatch.stream().map(transformation).forEachOrdered(internalList::add);
}

是正确的。

除此之外,addAll

中的优势
public void option1(List<S> newBatch) {
  internalList.addAll(newBatch.stream().map(transformation).collect(Collectors.toList()));
}

没有实际意义,因为Collector API不允许Stream向收集器提供有关预期大小的提示,并要求Stream评估每个元素的累加器函数,这只是{{1}在当前的实现中。

因此,在此方法可以从ArrayList::add获得任何好处之前,它会通过在addAll上重复调用ArrayList来填充add,包括潜在的容量增加操作。所以你可以毫不后悔地留在ArrayList

另一种方法是使用流构建器进行临时收集:

option2

流构建器使用spined缓冲区,在增加容量时不需要复制内容,但解决方案仍然受到最终收集步骤涉及填充没有适当初始容量的public class Accumulator<S, T> { private final Function<S, T> transformation; private final Stream.Builder<T> internal = Stream.builder(); public Accumulator(Function<S, T> transformation) { this.transformation = transformation; } public void addBatch(List<S> newBatch) { newBatch.stream().map(transformation).forEachOrdered(internal); } public List<T> finish() { return internal.build().collect(Collectors.toList()); } } 这一事实的影响。目前的实施情况)。

使用当前的实现,实现完成步骤

的效率要高得多
ArrayList

但这要求调用者提供public List<T> finish() { return Arrays.asList(internal.build().toArray(…)); } (因为我们不能对通用数组类型执行此操作),或者执行未经检查的操作(假装IntFunction<T[]>为{ {1}},这里可以,但仍然是一个讨厌的未经检查的操作。)