为流中已有的每个元素添加一个元素到流的末尾

时间:2017-09-18 11:57:16

标签: java java-8 java-stream concat

给定一个函数Function<T, T> f和一个Stream<T> ts什么是好的(良好的可读性,良好的性能)创建新的Stream<T>的方法,首先包含原始元素然后转换的元素f

有人可能认为这会奏效:

Stream.concat(ts, ts.map(f));

但是这不起作用而导致异常:

java.lang.IllegalStateException: stream has already been operated upon or closed

注意:订单确实很重要:原始元素必须以正确的顺序排在第一位,然后按顺序排列转换后的元素。

3 个答案:

答案 0 :(得分:7)

你不能打开一瓶葡萄酒,然后将瓶子交给另一个人并要求他再次打开

因此,我认为流的 nature 不可能做你要求的事情。

每个流都有一个“处理”链。你不能拥有两个

所以你能得到的最接近的就是“它的起源”,比如

Stream.concat(someList.stream(), someList.stream().map(f));
例如,

。当然,当你没有这个清单时,你可以选择:

List<Whatever> someList = ts.collect(Collectors.asList());

第一

答案 1 :(得分:6)

您可以实现包装源流的Spliterator。在内部,您将为每个处理过的元素创建“复制”元素,然后在源为空时切换到这些重复元素:

public class Duplicates<T> implements Spliterator<T> {
    private Spliterator<T> source;

    private Consumer<T>    addDuplicate;

    private Builder<T>     extrasStreamBuilder = Stream.builder();
    private Spliterator<T> extrasSpliterator;

    private Duplicates(Stream<T> source, UnaryOperator<T> f) {
        this.addDuplicate = t -> extrasStreamBuilder.add(f.apply(t));
        this.source = source.spliterator();
    }

    public static <T> Stream<T> of(Stream<T> source, UnaryOperator<T> f) {
        return StreamSupport.stream(new Duplicates<>(source, f), false);
    }

    @Override
    public boolean tryAdvance(Consumer<? super T> action) {
        boolean advanced = false;

        if (extrasSpliterator == null) {
            advanced = source.tryAdvance(addDuplicate.andThen(action));
        }

        if (!advanced) {
            if (extrasSpliterator == null) {
                extrasSpliterator = extrasStreamBuilder.build().spliterator();
            }
            advanced = extrasSpliterator.tryAdvance(action);
        }

        return advanced;
    }

    @Override
    public void forEachRemaining(Consumer<? super T> action) {
        if (extrasSpliterator == null) {
            source.forEachRemaining(addDuplicate.andThen(action));
            extrasSpliterator = extrasStreamBuilder.build().spliterator();
        }

        extrasSpliterator.forEachRemaining(action);
    }

    // other spliterator methods worked with default (Eclipse) implementation for the example below, but should probably delegate to source
}

public static void main(String[] args) {
    List<String> input = Arrays.asList("1", "2", "3");

    Stream<String> wrapper = Duplicates.of(input.stream(), i -> i + "0");

    wrapper.forEach(System.out::println);
}

// Output:
// 1
// 2
// 3
// 10
// 20
// 30

在流构建器中保留extras时,可能取决于您的用例是否在内存消耗方面足够高效。

在实际流处理之前收集和映射的优势在于,您只需遍历一次源。当检索元素需要很长时间或者元素的顺序可以在流之间改变时,这可能会有所帮助。

您还可以在复制之前首先将某些流操作链接到源,同样无需将中间结果收集到集合中。

答案 2 :(得分:3)

当它仅用于元素而不用于顺序(首先是原始项目,然后是修改过的),你可以使用flatMap:

Stream<T> s = ...;
Stream<T> result = s.flatMap(x -> Stream.of(x, f.apply(x));
result.forEach(System.out::println);

如果订单相关,可以问你为什么要使用流,因为你不会从懒惰的评估中受益......