Reputation: 81882
Given a function Function<T, T> f
and a Stream<T> ts
what is a good (nice readability, good performance) way of creating a new Stream<T>
which first contains the original elements and then the elements converted by f
.
One might think this would work:
Stream.concat(ts, ts.map(f));
But this doesn't work and results in an exception instead:
java.lang.IllegalStateException: stream has already been operated upon or closed
Note: that the order does matter: original elements have to come first in correct order, then the transformed elements in matching order.
Upvotes: 6
Views: 376
Reputation: 4555
You can implement a Spliterator
that wraps your source stream. Internally, you will create the "duplicate" element for each processed one, and then switch to those duplicates once the source is empty:
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
It might depend on your use case whether this is efficient enough in regards of memory consumption as you keep the extras
in the stream builder.
The advantage over collecting and mapping ahead of your actual stream processing is that you only traverse the source once. This might be helpful when retrieving the elements takes long or the order of elements can change between streams.
You are also able to first chain some stream operations to the source before duplicating, again without having to collect the intermediate result into a collection.
Upvotes: 6
Reputation: 141
When it's only for the elements and not for the order (first the original items, then the modified), you could use flatMap:
Stream<T> s = ...;
Stream<T> result = s.flatMap(x -> Stream.of(x, f.apply(x));
result.forEach(System.out::println);
If the order is relevant, one could ask why you want to use streams, because you won't benefit from lazy evaluation...
Upvotes: 3
Reputation: 140427
You can't open a bottle of wine and then pass the bottle to another person and ask him to open it again.
Thus I think this is not possible by the nature of streams to do what you ask for.
You have one chain of "processing" per stream. You can't have two.
So the closest you could get at would be working from "its origin", like
Stream.concat(someList.stream(), someList.stream().map(f));
for example. And of course, when you don't have that list, you could go for:
List<Whatever> someList = ts.collect(Collectors.asList());
first.
Upvotes: 7