为流中已有的每个元素添加一个元素到流的末尾
Adding an element to the end of a stream for each element already in the stream
给定一个函数 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
注意:顺序很重要:原始元素必须以正确的顺序排在第一位,然后是转换后的元素以匹配的顺序。
你不能打开一瓶酒,然后把瓶子递给另一个人,让他再打开。
因此我认为流的 性质 不可能按照您的要求进行操作。
每个流有 一个 链 "processing"。你不能有 两个.
所以最接近的是从 "its origin" 开始工作,比如
Stream.concat(someList.stream(), someList.stream().map(f));
例如。当然,如果您没有该列表,您可以选择:
List<Whatever> someList = ts.collect(Collectors.asList());
第一。
当只针对元素而不针对顺序(首先是原始项目,然后是修改后的项目)时,您可以使用 flatMap:
Stream<T> s = ...;
Stream<T> result = s.flatMap(x -> Stream.of(x, f.apply(x));
result.forEach(System.out::println);
如果顺序相关,有人可能会问为什么要使用流,因为您不会从惰性求值中获益...
您可以实现一个 Spliterator
来包装您的源流。在内部,您将为每个已处理的元素创建 "duplicate" 元素,然后在源为空后切换到这些重复项:
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
保留在流构建器中,这在内存消耗方面是否足够有效。
在实际流处理之前进行收集和映射的优势在于您只需遍历源一次。当检索元素需要很长时间或元素的顺序在流之间发生变化时,这可能会有所帮助。
您还可以在复制之前先将一些流操作链接到源,同样无需将中间结果收集到集合中。
给定一个函数 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
注意:顺序很重要:原始元素必须以正确的顺序排在第一位,然后是转换后的元素以匹配的顺序。
你不能打开一瓶酒,然后把瓶子递给另一个人,让他再打开。
因此我认为流的 性质 不可能按照您的要求进行操作。
每个流有 一个 链 "processing"。你不能有 两个.
所以最接近的是从 "its origin" 开始工作,比如
Stream.concat(someList.stream(), someList.stream().map(f));
例如。当然,如果您没有该列表,您可以选择:
List<Whatever> someList = ts.collect(Collectors.asList());
第一。
当只针对元素而不针对顺序(首先是原始项目,然后是修改后的项目)时,您可以使用 flatMap:
Stream<T> s = ...;
Stream<T> result = s.flatMap(x -> Stream.of(x, f.apply(x));
result.forEach(System.out::println);
如果顺序相关,有人可能会问为什么要使用流,因为您不会从惰性求值中获益...
您可以实现一个 Spliterator
来包装您的源流。在内部,您将为每个已处理的元素创建 "duplicate" 元素,然后在源为空后切换到这些重复项:
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
保留在流构建器中,这在内存消耗方面是否足够有效。
在实际流处理之前进行收集和映射的优势在于您只需遍历源一次。当检索元素需要很长时间或元素的顺序在流之间发生变化时,这可能会有所帮助。
您还可以在复制之前先将一些流操作链接到源,同样无需将中间结果收集到集合中。