Java 什么';将流中的元素添加到现有列表中的更好方法是什么?

Java 什么';将流中的元素添加到现有列表中的更好方法是什么?,java,collections,java-8,java-stream,Java,Collections,Java 8,Java Stream,我必须编写一些代码,多次将Java8流的内容添加到列表中,我很难找到最好的方法。根据我读到的(主要是这个问题:)和其他内容,我将其缩小为以下选项: import java.util.ArrayList; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; public class Accumulator<S, T> { private

我必须编写一些代码,多次将Java8流的内容添加到列表中,我很难找到最好的方法。根据我读到的(主要是这个问题:)和其他内容,我将其缩小为以下选项:

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);
    }
}
import java.util.ArrayList;
导入java.util.List;
导入java.util.function.function;
导入java.util.stream.collector;
公共类累加器{
私有最终功能转换;
private final List internalList=new ArrayList();
公共累加器(功能转换){
这个。转变=转变;
}
公共作废选项1(列出新批次){
internalList.addAll(newBatch.stream().map(transformation.collect)(Collectors.toList());
}
公共作废选项2(列出新批次){
newBatch.stream().map(转换).forEach(内部列表::添加);
}
}
其思想是,对于相同的
累加器
实例,将多次调用这些方法。选择是使用中间列表并在流外部调用
Collection.addAll()
,还是从流中为每个元素调用
Collection.add()

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

这两个选项中的一个是否明显优于另一个

编辑:JB Nizet有一个非常酷的方法,它将转换延迟到添加所有批次。在我的例子中,要求立即执行转换


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

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

假设您有一个
列表
,其中包含必须对其应用相同转换的N个批,您可以这样做

List<T> result = 
    batches.stream()
           .flatMap(batch -> batch.stream())
           .map(transformation)
           .collect(Collectors.toList());
列表结果=
batches.stream()
.flatMap(批处理->批处理.stream())
.map(转换)
.collect(Collectors.toList());

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

public void option2(List<S> newBatch) {
  newBatch.stream().map(transformation).forEachOrdered(internalList::add);
}
由于
收集器
API不允许流向收集器提供有关预期大小的提示,并且要求流为每个元素计算累加器函数,这在当前实现中只不过是
ArrayList::add

因此,在这种方法能够从
addAll
中获益之前,它通过在
ArrayList
上反复调用
add
来填充
ArrayList
,包括潜在的容量增加操作。因此,您可以继续使用
选项2
,而不会感到遗憾

另一种方法是对临时集合使用流生成器:

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());
    }
}

但是这需要调用者提供一个
IntFunction
(因为我们不能对泛型数组类型这样做),或者执行一个未检查的操作(假装
对象[]
t[]
,这在这里是可以的,但仍然是一个讨厌的未检查操作)。

反汇编字节码(javap)可能会帮助您了解不要过早进行优化。做任何更干净的事情,并且只有在遇到性能问题时才使用探查器检查此代码。我认为调用
addAll()
没有任何好处。请注意,如果执行并行流,结果将不同,除非将
forEach()
更改为
forEachOrdered()
。为什么需要它“立即执行转换”?如果您关心“函数式编程的精神”,在执行转换时应该无关紧要。这是最好的选择。因为这将创建固定大小的列表。回答得好,但它要求您可以推迟对早期批的处理,直到所有批都准备好。情况可能并非总是如此。回答得好,我不会想到这一点,尽管在我的情况下,我尝试不定义呃,处理,就如@Andreas所说的,我正在编辑这个问题accordingly@rana_stack你认为
Collectors.toList
会创建一个预先确定的列表吗?这远远不是。看看or Holger的答案,它描述了实际发生的事情。
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());
    }
}
public List<T> finish() {
    return Arrays.asList(internal.build().toArray(…));
}