AlikElzin-kilaka
AlikElzin-kilaka

Reputation: 35991

How do I get the first element while continue streaming?

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

Answers (7)

Andronicus
Andronicus

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

Holger
Holger

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

ernest_k
ernest_k

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

Ruslan
Ruslan

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

Lino
Lino

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

Benjamin Urquhart
Benjamin Urquhart

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

Adil Shaikh
Adil Shaikh

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

Related Questions