8

I'm looking for a clean and efficient way to apply a consumer to one element of a non parallel stream without closing the stream.

I mean I want to replace

AtomicBoolean firstOneDone = new AtomicBoolean();
lines.forEach(line -> {
    if (!firstOneDone.get()) {
        // handle first line
        firstOneDone.set(true);
    } else {
        // handle any other line
    }
})

with something alike

lines.forFirst(header -> {
    // handle first line        
}).forEach(line -> {
    // handle any other line
})

I don't want to do two passes over the whole stream (copy or recreate the stream, peek, etc.), or just move the boolean/test to another location like a function wrapper.

Is that possible or is the fundamental model of streams not compatible with this kind of partial reading ?

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • @assylias that's "doing two passes over the whole stream" – Florian Margaine Mar 03 '15 at 10:05
  • 2
    @FlorianMargaine Streams are typically lazy so the first line will only "read" the first element of the stream and won't process the rest of the stream. – assylias Mar 03 '15 at 10:06
  • @FlorianMargaine In fact it doesn't do 2 passes with a non parallelized stream. This looks like a valid and interesting answer. – Denys Séguret Mar 03 '15 at 10:06
  • Oh, interesting. My bad then! – Florian Margaine Mar 03 '15 at 10:07
  • @FlorianMargaine That would be pretty inefficient if it was designed like that, as a `filter` and a `findFirst` would result to check all the elements of the Stream while not necessary (and that wouldn't even work for infinite streams) ;-) – Alexis C. Mar 03 '15 at 10:09
  • 1
    similar: http://stackoverflow.com/questions/26595020/getting-the-next-item-from-a-java-8-stream – Alex - GlassEditor.com Mar 03 '15 at 10:23
  • @Alex It's very similar and the not accepted answer is interesting, thanks – Denys Séguret Mar 03 '15 at 10:31
  • If it must be done without a (more or less hidden) boolean (see my deleted answer!): leaving Java streams aside, can you explain how to distinguish (in any sequence) the head from the tail? If you can do that, then you can do it in a Stream. – laune Mar 03 '15 at 10:36
  • @dystroy An alternative way of using it would be: http://pastebin.com/NkQLDDTE i.e. using collect. it generates 2 streams in only a single iteration – Jatin Mar 03 '15 at 10:54
  • @Jatin This just moves the boolean in fact – Denys Séguret Mar 03 '15 at 11:10

3 Answers3

10

No, it's not possible because your stream pipeline is closed with each "final" operation. An alternative is the following, using the Stream's iterator. You'll have only one iterator. I guess that's what you actually want since you insist on creating only one stream. However you'll have to skip the "functional" part.

Stream<String> strings = ... ;
Iterator<String> stringsIt = strings.iterator();
if (stringsIt.hasNext()) {
  System.out.printf("header: %s%n", stringsIt.next());
  while (stringsIt.hasNext()) {
    System.out.printf("line: %s%n", stringsIt.next());
  }
}

An alternative, with ZouZou's comments:

Stream<String> strings = ... ;
Iterator<String> stringsIt = strings.iterator();
if (stringsIt.hasNext()) {
  System.out.printf("header: %s%n", stringsIt.next());
  stringsIt.forEachRemaining(line -> { System.out.printf("line: %s%n", line); });
}

A final answer with all functional is actually the following:

Stream<String> lines = ... ;
Spliterator<String> linesIt = lines.spliterator();
linesIt.tryAdvance(header -> { System.out.printf("header: %s%n", header); });
linesIt.forEachRemaining(line -> { System.out.printf("line: %s%n", line); });
Olivier Grégoire
  • 33,839
  • 23
  • 96
  • 137
1

Since you only seem to be consuming lines, you can just grab the Iterator from it; note, code below assumes a non empty stream:

final Iterator<String> iterator = theStream.iterator();

process(iterator.next());

iterator.forEachRemaining(doSomething());
fge
  • 119,121
  • 33
  • 254
  • 329
1

I don't think what you are describing is actually possible. Even the first code you posted is not something I would recommend using. If the forEach gets executed in a parallel way, your if(first) might not work correctly.

If the class that is holding your data is a Collection you can just grab the first one in the list with the iterator.

If you really have to use streams you can do something like:

// assuming getStreamFromSomewhere recreates the Stream and is not
// very time consuming
getStreamFromSomewhere().limit(1).forEach(doFirst);
getStreamFromSomewhere().skip(1).forEach(doRest);

Streams are lazy so it won't actually go through the whole stream twice.

It is important to remember that the Stream API is not holding any data itself. Any call on a Stream is more like plan about what to do with the data and how it flows from source to destination. Random access is not a part of that.

mhlz
  • 3,497
  • 2
  • 23
  • 35