我可以在Java 8中复制Stream吗?

时间:2014-06-29 09:36:33

标签: java java-8 java-stream

有时我想对流执行一组操作,然后以不同的方式处理生成的流与其他操作。

我可以在不必指定两次常见初始操作的情况下执行此操作吗?

例如,我希望存在以下dup()方法:

Stream [] desired_streams = IntStream.range(1, 100).filter(n -> n % 2 == 0).dup();
Stream stream14 = desired_streams[0].filter(n -> n % 7 == 0); // multiples of 14
Stream stream10 = desired_streams[1].filter(n -> n % 5 == 0); // multiples of 10

10 个答案:

答案 0 :(得分:42)

无法以这种方式复制流。但是,您可以通过将公共部分移动到方法或lambda表达式来避免代码重复。

Supplier<IntStream> supplier = () ->
    IntStream.range(1, 100).filter(n -> n % 2 == 0);
supplier.get().filter(...);
supplier.get().filter(...);

答案 1 :(得分:26)

一般情况下不可能。

如果要复制输入流或输入迭代器,您有两个选择:

一种。将所有内容保存在集合中,例如List<>

假设您将流复制为两个流s1s2。如果您在n1s1元素中使用n2元素中有高级s2元素,则必须将|n2 - n1|元素保留在内存中,以保持同步。如果您的流是无限的,则可能没有所需存储的上限。

看一下Python的tee(),看看它需要什么:

  

此itertool可能需要大量辅助存储(取决于需要存储多少临时数据)。通常,如果一个迭代器在另一个迭代器启动之前使用了大部分或全部数据,则使用list()代替tee()会更快。

B中。如果可能:复制创建元素的生成器的状态

要使此选项起作用,您可能需要访问流的内部工作方式。换句话说,生成器 - 创建元素的部分 - 应该首先支持复制。 [OP:请参阅此great answer,作为如何为问题中的示例做到这一点的示例]

它不适用于用户的输入,因为您必须复制整个“外部世界”的状态。 Java Stream不支持复制,因为它设计得尽可能通用,专门用于处理文件,网络,键盘,传感器,随机性等。[OP:另一个例子是读取温度传感器的流一经请求。如果不存储读数副本,则无法复制。

这不仅仅是Java中的情况;这是一般规则。您可以看到C ++中的std::istream仅支持移动语义,而不支持复制语义(“复制构造函数(已删除)”),因此(以及其他)。

答案 2 :(得分:6)

如果你在一个副本中消耗了你已经消耗过的元素,但在另一个副本中却没有消耗元素,那么这是可能的。

我们为jOOλ中的流实施了duplicate()方法,这是我们为改进jOOQ的集成测试而创建的开源库。基本上,你可以写:

Tuple2<Seq<Integer>, Seq<Integer>> desired_streams = Seq.seq(
    IntStream.range(1, 100).filter(n -> n % 2 == 0).boxed()
).duplicate();

(注意:我们目前需要打包流,因为我们还没有实现IntSeq

在内部,有一个LinkedList缓冲区,用于存储从一个流中消耗但不从另一个流中消耗的所有值。如果您的两个流以相同的速率消耗,那可能会有效率。

以下是该算法的工作原理:

static <T> Tuple2<Seq<T>, Seq<T>> duplicate(Stream<T> stream) {
    final LinkedList<T> gap = new LinkedList<>();
    final Iterator<T> it = stream.iterator();

    @SuppressWarnings("unchecked")
    final Iterator<T>[] ahead = new Iterator[] { null };

    class Duplicate implements Iterator<T> {
        @Override
        public boolean hasNext() {
            if (ahead[0] == null || ahead[0] == this)
                return it.hasNext();

            return !gap.isEmpty();
        }

        @Override
        public T next() {
            if (ahead[0] == null)
                ahead[0] = this;

            if (ahead[0] == this) {
                T value = it.next();
                gap.offer(value);
                return value;
            }

            return gap.poll();
        }
    }

    return tuple(seq(new Duplicate()), seq(new Duplicate()));
}

More source code here

事实上,使用jOOλ,你就可以像这样写一个完整的单行代码:

Tuple2<Seq<Integer>, Seq<Integer>> desired_streams = Seq.seq(
    IntStream.range(1, 100).filter(n -> n % 2 == 0).boxed()
).duplicate()
 .map1(s -> s.filter(n -> n % 7 == 0))
 .map2(s -> s.filter(n -> n % 5 == 0));

// This will yield 14, 28, 42, 56...
desired_streams.v1.forEach(System.out::println)

// This will yield 10, 20, 30, 40...
desired_streams.v2.forEach(System.out::println);

答案 3 :(得分:4)

您还可以将流生成移动到单独的方法/函数中,该方法/函数返回此流并将其调用两次。

答案 4 :(得分:3)

无论哪种,

  • 将初始化移至方法中,然后再次调用该方法

这样做的好处是可以明确你正在做什么,也适用于无限流。

  • 收集流然后重新流式传输

在你的例子中:

final int[] arr = IntStream.range(1, 100).filter(n -> n % 2 == 0).toArray();

然后

final IntStream s = IntStream.of(arr);

答案 5 :(得分:2)

更新:有效。在原始答案的文本之后,请参阅下面的说明。

我有多傻。我需要做的就是:

Stream desired_stream = IntStream.range(1, 100).filter(n -> n % 2 == 0);
Stream stream14 = desired_stream.filter(n -> n % 7 == 0); // multiples of 14
Stream stream10 = desired_stream.filter(n -> n % 5 == 0); // multiples of 10

解释为什么这不起作用:

如果您对其进行编码并尝试收集两个流,则第一个将收集正常,但尝试流式传输第二个将抛出异常:java.lang.IllegalStateException: stream has already been operated upon or closed

详细说明,流是有状态对象(顺便说一句,它不能被重置或重绕)。您可以将它们视为迭代器,而迭代器又像指针一样。因此stream14stream10可以被认为是对同一指针的引用。一直使用第一个流将导致指针“越过末尾”。尝试使用第二个流就像尝试访问已经“结束”的指针一样,这自然是非法操作。

如接受的答案所示,创建流的代码必须执行两次,但可以划分为Supplier lambda或类似的构造。

完整测试代码:保存到Foo.java,然后javac Foo.java,然后java Foo

import java.util.stream.IntStream;

public class Foo {
  public static void main (String [] args) {
    IntStream s = IntStream.range(0, 100).filter(n -> n % 2 == 0);
    IntStream s1 = s.filter(n -> n % 5 == 0);
    s1.forEach(n -> System.out.println(n));
    IntStream s2 = s.filter(n -> n % 7 == 0);
    s2.forEach(n -> System.out.println(n));
  }
}

<强>输出:

$ javac Foo.java
$ java Foo
0
10
20
30
40
50
60
70
80
90
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203)
    at java.util.stream.IntPipeline.<init>(IntPipeline.java:91)
    at java.util.stream.IntPipeline$StatelessOp.<init>(IntPipeline.java:592)
    at java.util.stream.IntPipeline$9.<init>(IntPipeline.java:332)
    at java.util.stream.IntPipeline.filter(IntPipeline.java:331)
    at Foo.main(Foo.java:8)

答案 6 :(得分:1)

我认为在空流中使用 Concat 可以满足您的需要。 尝试这样的事情:

Stream<Integer> concat = Stream.concat(Stream.of(1, 2), Stream.empty());

答案 7 :(得分:0)

对于非无限流,如果可以访问源,则直接访问源:

@Test
public void testName() throws Exception {
    List<Integer> integers = Arrays.asList(1, 2, 4, 5, 6, 7, 8, 9, 10);
    Stream<Integer> stream1 = integers.stream();
    Stream<Integer> stream2 = integers.stream();

    stream1.forEach(System.out::println);
    stream2.forEach(System.out::println);
}
  

打印

     

1   2   4   5   6   7   8   9   10

     

1   2   4   5   6   7   8   9   10

针对您的情况:

Stream originalStream = IntStream.range(1, 100).filter(n -> n % 2 == 0)

List<Integer> listOf = originalStream.collect(Collectors.toList())

Stream stream14 = listOf.stream().filter(n -> n % 7 == 0);
Stream stream10 = listOf.stream().filter(n -> n % 5 == 0);

关于演奏等,请阅读别人的答案;)

答案 8 :(得分:0)

我用this很好的答案写了以下课程:

public class SplitStream<T> implements Stream<T> {
    private final Supplier<Stream<T>> streamSupplier;

    public SplitStream(Supplier<Stream<T>> t) {
        this.streamSupplier = t;
    }

    @Override
    public Stream<T> filter(Predicate<? super T> predicate) {
        return streamSupplier.get().filter(predicate);
    }

    @Override
    public <R> Stream<R> map(Function<? super T, ? extends R> mapper) {
        return streamSupplier.get().map(mapper);
    }

    @Override
    public IntStream mapToInt(ToIntFunction<? super T> mapper) {
        return streamSupplier.get().mapToInt(mapper);
    }

    @Override
    public LongStream mapToLong(ToLongFunction<? super T> mapper) {
        return streamSupplier.get().mapToLong(mapper);
    }

    @Override
    public DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper) {
        return streamSupplier.get().mapToDouble(mapper);
    }

    @Override
    public <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) {
        return streamSupplier.get().flatMap(mapper);
    }

    @Override
    public IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper) {
        return streamSupplier.get().flatMapToInt(mapper);
    }

    @Override
    public LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper) {
        return streamSupplier.get().flatMapToLong(mapper);
    }

    @Override
    public DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper) {
        return streamSupplier.get().flatMapToDouble(mapper);
    }

    @Override
    public Stream<T> distinct() {
        return streamSupplier.get().distinct();
    }

    @Override
    public Stream<T> sorted() {
        return streamSupplier.get().sorted();
    }

    @Override
    public Stream<T> sorted(Comparator<? super T> comparator) {
        return streamSupplier.get().sorted(comparator);
    }

    @Override
    public Stream<T> peek(Consumer<? super T> action) {
        return streamSupplier.get().peek(action);
    }

    @Override
    public Stream<T> limit(long maxSize) {
        return streamSupplier.get().limit(maxSize);
    }

    @Override
    public Stream<T> skip(long n) {
        return streamSupplier.get().skip(n);
    }

    @Override
    public void forEach(Consumer<? super T> action) {
        streamSupplier.get().forEach(action);
    }

    @Override
    public void forEachOrdered(Consumer<? super T> action) {
        streamSupplier.get().forEachOrdered(action);
    }

    @Override
    public Object[] toArray() {
        return streamSupplier.get().toArray();
    }

    @Override
    public <A> A[] toArray(IntFunction<A[]> generator) {
        return streamSupplier.get().toArray(generator);
    }

    @Override
    public T reduce(T identity, BinaryOperator<T> accumulator) {
        return streamSupplier.get().reduce(identity, accumulator);
    }

    @Override
    public Optional<T> reduce(BinaryOperator<T> accumulator) {
        return streamSupplier.get().reduce(accumulator);
    }

    @Override
    public <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner) {
        return streamSupplier.get().reduce(identity, accumulator, combiner);
    }

    @Override
    public <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner) {
        return streamSupplier.get().collect(supplier, accumulator, combiner);
    }

    @Override
    public <R, A> R collect(Collector<? super T, A, R> collector) {
        return streamSupplier.get().collect(collector);
    }

    @Override
    public Optional<T> min(Comparator<? super T> comparator) {
        return streamSupplier.get().min(comparator);
    }

    @Override
    public Optional<T> max(Comparator<? super T> comparator) {
        return streamSupplier.get().max(comparator);
    }

    @Override
    public long count() {
        return streamSupplier.get().count();
    }

    @Override
    public boolean anyMatch(Predicate<? super T> predicate) {
        return streamSupplier.get().anyMatch(predicate);
    }

    @Override
    public boolean allMatch(Predicate<? super T> predicate) {
        return streamSupplier.get().allMatch(predicate);
    }

    @Override
    public boolean noneMatch(Predicate<? super T> predicate) {
        return streamSupplier.get().noneMatch(predicate);
    }

    @Override
    public Optional<T> findFirst() {
        return streamSupplier.get().findFirst();
    }

    @Override
    public Optional<T> findAny() {
        return streamSupplier.get().findAny();
    }

    @Override
    public Iterator<T> iterator() {
        return streamSupplier.get().iterator();
    }

    @Override
    public Spliterator<T> spliterator() {
        return streamSupplier.get().spliterator();
    }

    @Override
    public boolean isParallel() {
        return streamSupplier.get().isParallel();
    }

    @Override
    public Stream<T> sequential() {
        return streamSupplier.get().sequential();
    }

    @Override
    public Stream<T> parallel() {
        return streamSupplier.get().parallel();
    }

    @Override
    public Stream<T> unordered() {
        return streamSupplier.get().unordered();
    }

    @Override
    public Stream<T> onClose(Runnable closeHandler) {
        return streamSupplier.get().onClose(closeHandler);
    }

    @Override
    public void close() {
        streamSupplier.get().close();
    }
}

当您调用其类的任何方法时,它都会将调用委托给

streamSupplier.get()

所以,而不是:

Supplier<IntStream> supplier = () ->
    IntStream.range(1, 100).filter(n -> n % 2 == 0);
supplier.get().filter(...);
supplier.get().filter(...);

您可以这样做:

SplitStream<Integer> stream = 
    new SplitStream<>(() -> IntStream.range(1, 100).filter(n -> n % 2 == 0).boxed());
stream.filter(...);
stream.filter(...);

您可以扩展它以与IntStream,DoubleStream等配合使用...

答案 9 :(得分:0)

从 Java 12 开始,我们有 Collectors::teeing 允许我们将主流管道的元素传递给 2 个或多个下游收集器。

根据您的示例,我们可以执行以下操作:

@Test
void shouldProcessStreamElementsInTwoSeparateDownstreams() {
    class Result {
        List<Integer> multiplesOf7;
        List<Integer> multiplesOf5;

        Result(List<Integer> multiplesOf7, List<Integer> multiplesOf5) {
            this.multiplesOf7 = multiplesOf7;
            this.multiplesOf5 = multiplesOf5;
        }
    }

    var result = IntStream.range(1, 100)
            .filter(n -> n % 2 == 0)
            .boxed()
            .collect(Collectors.teeing(
                    Collectors.filtering(n -> n % 7 == 0, Collectors.toList()),
                    Collectors.filtering(n -> n % 5 == 0, Collectors.toList()),
                    Result::new
            ));

    assertTrue(result.multiplesOf7.stream().allMatch(n -> n % 7 == 0));
    assertTrue(result.multiplesOf5.stream().allMatch( n -> n % 5 == 0));
}

还有许多其他收集器可以做其他事情,例如通过在下游使用 Collectors::mapping,您可以从同一个源获得两个不同的对象/类型,如 this article 所示。