有时我想对流执行一组操作,然后以不同的方式处理生成的流与其他操作。
我可以在不必指定两次常见初始操作的情况下执行此操作吗?
例如,我希望存在以下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
答案 0 :(得分:42)
无法以这种方式复制流。但是,您可以通过将公共部分移动到方法或lambda表达式来避免代码重复。
Supplier<IntStream> supplier = () ->
IntStream.range(1, 100).filter(n -> n % 2 == 0);
supplier.get().filter(...);
supplier.get().filter(...);
答案 1 :(得分:26)
一般情况下不可能。
如果要复制输入流或输入迭代器,您有两个选择:
List<>
假设您将流复制为两个流s1
和s2
。如果您在n1
和s1
元素中使用n2
元素中有高级s2
元素,则必须将|n2 - n1|
元素保留在内存中,以保持同步。如果您的流是无限的,则可能没有所需存储的上限。
看一下Python的tee()
,看看它需要什么:
此itertool可能需要大量辅助存储(取决于需要存储多少临时数据)。通常,如果一个迭代器在另一个迭代器启动之前使用了大部分或全部数据,则使用
list()
代替tee()
会更快。
要使此选项起作用,您可能需要访问流的内部工作方式。换句话说,生成器 - 创建元素的部分 - 应该首先支持复制。 [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()));
}
事实上,使用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
。
详细说明,流是有状态对象(顺便说一句,它不能被重置或重绕)。您可以将它们视为迭代器,而迭代器又像指针一样。因此stream14
和stream10
可以被认为是对同一指针的引用。一直使用第一个流将导致指针“越过末尾”。尝试使用第二个流就像尝试访问已经“结束”的指针一样,这自然是非法操作。
如接受的答案所示,创建流的代码必须执行两次,但可以划分为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 所示。