Reputation: 35991
I have a stream of generic items. I'd like to print the class name of the first item + the toString()
of all the items.
If I had an Iterable, it would look like this:
Iterable<E> itemIter = ...;
boolean first = true;
for (E e : itemIter) {
if (first) {
first = false;
System.out.println(e.getClass().getSimpleName());
}
System.out.println(e);
}
Can I do this on a stream (Stream<T>
) with the stream API?
* Please note that it's a question about streams - not about iterators. I have a stream - not an iterator.
Upvotes: 30
Views: 5905
Reputation: 26036
Native solution: Stream
in Java is not reusable. This means, that consuming stream can be done only once. If you get the first element from a stream, you can iterate over it one more time.
Workaround would be to create another stream same as the first one or getting the first item and then creating a stream, something like that:
Stream<E> stream = StreamSupport.stream(Spliterators.spliteratorUnknownSize(sourceIterator, Spliterator.ORDERED), false);
E firstElement = itemIter.next();
stream.foreach(...);
Edit
There is not really any way to "copy" a stream, you need to keep an iterator / collection. More about it here. When it comes to memory once stream is exhausted, it can be collected by garbage collector as there is no use from it. Stream itself does not take more space than iterator it originates from. Bear in mind, that streams can be potentially infinite. Elements currently manipulated are stored in memory.
Upvotes: 10
Reputation: 298123
If your starting point is a Stream
and you want to retain all of its properties and the laziness, the following solution will do:
public static <E> Stream<E> forFirst(Stream<E> stream, Consumer<? super E> c) {
boolean parallel = stream.isParallel();
Spliterator<E> sp = stream.spliterator();
return StreamSupport.stream(() -> {
if(sp.getExactSizeIfKnown() == 0) return sp;
Stream.Builder<E> b = Stream.builder();
if(!sp.tryAdvance(b.andThen(c))) return sp;
return Stream.concat(b.build(), StreamSupport.stream(sp, parallel)).spliterator();
}, sp.characteristics(), parallel);
}
E.g. when you use it with
List<String> list = new ArrayList<>(List.of("foo", "bar", "baz"));
Stream<String> stream = forFirst(
list.stream().filter(s -> s.startsWith("b")),
s -> System.out.println(s+" ("+s.getClass().getSimpleName()+')')
).map(String::toUpperCase);
list.add(1, "blah");
System.out.println(stream.collect(Collectors.joining(" | ")));
it will print
blah (String)
BLAH | BAR | BAZ
demonstrating that the processing will not start before commencing the terminal operation (collect
), hence reflecting the preceding update to the source List
.
Upvotes: 10
Reputation: 45309
You can also use an boolean atomic reference:
AtomicReference<Boolean> first = new AtomicReference<Boolean>(Boolean.TRUE);
stream.forEach(e ->
System.out.println("First == " + first.getAndUpdate(b -> false)));
Upvotes: 0
Reputation: 6290
There is StreamEx
library that extends standard Java's Stream API. Using StreamEx.of(Iterator)
and peekFirst
:
StreamEx.of(itemIter.iterator())
.peekFirst(e -> System.out.println(e.getClass().getSimpleName()))
.forEach(System.out::println);
Upvotes: 19
Reputation: 19926
You could use peek
for that:
AtomicBoolean first = new AtomicBoolean(true);
StreamSupport.stream(itemIter.spliterator(), false)
.peek(e -> {
if(first.get()) {
System.out.println(e.getClass().getSimpleName());
first.set(false);
}
})
...
Upvotes: 0
Reputation: 1649
You can abuse reduction:
Stream<E> stream = ...;
System.out.println(stream
.reduce("",(out,e) ->
out + (out.isEmpty() ? e.getClass().getSimpleName()+"\n" : "")
+ e));
Upvotes: 6
Reputation: 44740
One workaround is to do it like this -
import java.util.*;
import java.util.stream.Collectors;
public class MyClass {
static int i = 0;
static int getCounter(){
return i;
}
static void incrementCounter(){
i++;
}
public static void main(String args[]) {
List<String> list = Arrays.asList("A", "B", "C", "D", "E", "F", "G");
List<String> answer = list.stream().filter(str -> {if(getCounter()==0) {System.out.println("First Element : " + str);} incrementCounter(); return true;}).
collect(Collectors.toList());
System.out.println(answer);
}
}
Output :
First Element : A
[A, B, C, D, E, F, G]
Upvotes: 0