我必须编写一些代码,将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
作为占位符,用于需要在流上执行的任何操作
答案 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}},这里可以,但仍然是一个讨厌的未经检查的操作。)