Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handle one element in a stream without closing it [duplicate]

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 ?

like image 470
Denys Séguret Avatar asked Mar 03 '15 09:03

Denys Séguret


People also ask

Can we use same stream twice?

From the documentation: A stream should be operated on (invoking an intermediate or terminal stream operation) only once. A stream implementation may throw IllegalStateException if it detects that the stream is being reused. So the answer is no, streams are not meant to be reused.

What is the function used to duplicate a stream?

CopyTo(Stream) Reads the bytes from the current stream and writes them to another stream. Both streams positions are advanced by the number of bytes copied.

Why we need to close the stream objects always after usage?

You should always close a stream in order to free open resources on your OS. Opening a stream always returns an identifier which you can use to close it wherever you are in your code (as long as the identifier is valid), whether their from another method or class.

How does stream distinct work?

Java Stream distinct() MethodIf the stream is ordered, the encounter order is preserved. It means that the element occurring first will be present in the distinct elements stream. If the stream is unordered, then the resulting stream elements can be in any order. Stream distinct() is a stateful intermediate operation.


3 Answers

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); });
like image 87
Olivier Grégoire Avatar answered Sep 23 '22 23:09

Olivier Grégoire


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.

like image 27
mhlz Avatar answered Sep 25 '22 23:09

mhlz


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());
like image 23
fge Avatar answered Sep 24 '22 23:09

fge