30

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.

Andronicus
  • 25,419
  • 17
  • 47
  • 88
AlikElzin-kilaka
  • 34,335
  • 35
  • 194
  • 277
  • Please note that it's a question about streams - not about iterators. I have a stream - not an iterator. – AlikElzin-kilaka Mar 06 '19 at 16:38
  • @AndrewTobilko - I don't have an `Iterable` - just `Stream`. – AlikElzin-kilaka Mar 06 '19 at 19:29
  • Note that, if you have a stream, you can [get an iterator](https://docs.oracle.com/javase/8/docs/api/java/util/stream/BaseStream.html#iterator--) (and, if you need one, [also an iterable](https://stackoverflow.com/questions/31702989/how-to-iterate-with-foreach-loop-over-java-8-stream/31703176#31703176)). – Ilmari Karonen Mar 07 '19 at 08:47
  • @IlmariKaronen, if I convert the stream to an iterator, I might loose the concurrency capability. – AlikElzin-kilaka Mar 04 '21 at 07:35

7 Answers7

17

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);
Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
Ruslan
  • 6,090
  • 1
  • 21
  • 36
  • I wonder how they've implemented it, probably like one of the solutions already given here – Lino Mar 06 '19 at 16:50
  • 1
    note that the method has some limitations (mentioned in the docs) and "exists mainly to support debugging". – Andrew Tobilko Mar 06 '19 at 16:55
  • @Lino combine [this answer](https://stackoverflow.com/a/40636566/2711488) which performs an action for the first element (but also consumes it) and [this answer](https://stackoverflow.com/a/46077388/2711488) which will push the first item back after examining it and you have the necessary tools to construct such an operation. – Holger Mar 06 '19 at 17:36
  • 2
    By the way, it’s preferable to use `StreamEx.of(itemIter.spliterator())`, which may carry additional meta information to the stream (depending on the actual `Iterable`), potentially improving the performance. – Holger Mar 06 '19 at 17:48
10

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.

Andronicus
  • 25,419
  • 17
  • 47
  • 88
  • 1
    @AlikElzin-kilaka please don't ask about the memory implications :) To me, it's obvious that none of Stream API solutions will outdo the simple loop approach mentioned by you (I'd go with a fori loop, though) in any way (performance, readability, maintenance). I honestly don't understand why you want streams here. – Andrew Tobilko Mar 06 '19 at 16:44
  • Why do you assume that it's safe to cast: `(List) itemIter`? – ernest_k Mar 06 '19 at 16:46
  • @ernest_k it doesn't really matter, I don't know the context (what `itemIter` really is). Let's just treat it as a pseudocode, that can be compiled, just to show the idea :) – Andronicus Mar 06 '19 at 16:48
  • 1
    Well, it doesn’t even compile. Since objects are not an `Iterable` and an `Iterator` at the same time, you can’t cast it to `List` and expect it to also have a `next()` method. Once you fixed this, you don’t need the questionable type cast: `E firstElement = itemIter.iterator().next(); Stream stream = StreamSupport.stream(itemIter.spliterator(), false);` – Holger Mar 06 '19 at 17:44
10

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
6

You can abuse reduction:

Stream<E> stream = ...;
System.out.println(stream
                   .reduce("",(out,e) -> 
                   out + (out.isEmpty() ? e.getClass().getSimpleName()+"\n" : "")
                   + e));
Benjamin Urquhart
  • 1,626
  • 12
  • 18
0

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);
        }
    })
    ...
Lino
  • 19,604
  • 6
  • 47
  • 65
  • 1
    In that sense, I guess the code in question would be much better even if the iterable is defined. Keeping in mind there is nothing like `itemIter.stream()` available up front. – Naman Mar 06 '19 at 16:36
  • @nullpointer you're right, I've edited my answer to use StreamSupport – Lino Mar 06 '19 at 16:46
  • 1
    The action passed to `peek` will be executed in the processing order which is not guaranteed to be the Stream’s encounter order. – Holger Mar 06 '19 at 17:45
0

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]
Adil Shaikh
  • 44,509
  • 17
  • 89
  • 111
  • 1
    requires you to always reset the value though, and will not work in a multi thread environment – Lino Mar 06 '19 at 16:52
0

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)));
ernest_k
  • 44,416
  • 5
  • 53
  • 99