user7284379
user7284379

Reputation: 83

takeWhile, dropWhile laziness java9

In scala those methods works fine but in java9 dropWhile works differently I think.

Here is example for takeWhile

Stream.of("a", "b", "c", "de", "f", "g", "h")
                .peek(System.out::println)
                .takeWhile(s -> s.length() <= 1)
                .collect(Collectors.toList());

Output is fine: a, b, c, de, [a, b, c] It does not process elements after "de" so it works as expected

But dropWhile works in different way that I expect:

Stream.of("a", "b", "c", "de", "f", "g", "h")
                .peek(s -> System.out.print(s + ", "))
                .dropWhile(s -> s.length() <= 1)
                .collect(Collectors.toList());

Output is: a, b, c, de, f, g, h, [de, f, g, h]

So it's not stopping after "de" element, it's processing whole collection.

Why is it processing whole collection? I know, that it's needed to take all elements and collect it to the List but shouldn't it stop processing after "de" element?

Upvotes: 8

Views: 2736

Answers (4)

Eugene
Eugene

Reputation: 120998

I'm probably going to state the obvious here, but it makes sense for me, so might help you as well.

takeWhile takes the first n elements until the Predicate is hit (it returns true). So if you have let's say 7 elements in your incoming stream and takeWhile will return true for the first 3, then the resulting stream will contain 3 elements.

You can think about it like this:

Stream result = Stream.empty();
while(predicate.apply(streamElement)){ // takeWhile predicate
   result.add(streamElement);
   streamElement = next(); // move to the next element
}
return result; 

dropWhile says drop the elements until the Predicate is hit(it returns true). So after dropping your 3 elements, what will your result stream look like? To know that we need to iterate all other elements, that is why peek reports the rest.

I also like the analogy to dropWhile from a Set (as opposed to a List). Suppose this is your set :

 Set<String> mySet = Set.of("A", "B", "CC", "D");

And yo will do a dropWhile here with the same predicate (because it's not a List and you do not have encounter order); after the dropWhile operation there is no way to know the result unless you iterate all elements that where left.

Upvotes: 4

Holger
Holger

Reputation: 298489

It seems, there is a fundamental misunderstanding about how peek works. It is not associated with the next subsequently chained operation, like dropWhile, but the entire Stream pipeline behind it. And it does not make a distinction between “processing elements” and “take all elements”.

So the simple code

Stream.of("a", "b", "c", "de", "f", "g", "h")
      .peek(System.out::println)
      .collect(Collectors.toList());

“takes all elements”, but prints them as they are passed from the Stream source to the Collector.

In your example, it makes no difference, whether an element is passed to the predicate of dropWhile or directly to the Collector, in either case, it will be reported by the peek operation placed before both.

If you use

Stream.of("a", "b", "c", "de", "f", "g", "h")
      .dropWhile(s -> {
          System.out.println("dropWhile: "+s);
          return s.length() <= 1;
      })
      .peek(s -> System.out.println("collecting "+s))
      .collect(Collectors.toList());

instead, it will print

dropWhile: a
dropWhile: b
dropWhile: c
dropWhile: de
collecting de
collecting f
collecting g
collecting h

showing how the evaluation of dropWhile’s predicate stops after the first unaccepted element whereas the transfer to the Collector starts with that element.

This differs from the takeWhile where both, the predicate evaluation and the collector, stop consuming elements, so there is no consumer left and the entire Stream pipeline can stop iterating the source.

Upvotes: 11

Nicolai Parlog
Nicolai Parlog

Reputation: 51130

TL;DR

Always have a look at the entire stream, particularly the terminal operation to judge how the pipeline behaves. It is collect that influences the printed elements as much as takeWhile or dropWhile.

In Detail

I'm not sure whether you disagree with the output or the resulting list. In any case it is neither takeWhile nor dropWhile that is processing the stream but the terminal operation, collect in this case. It has the task to collect all elements in the stream and hence "pulls" elements through it until the stream reports to contain no more elements.

This is the case with takeWhile once the condition becomes false for the first time. After it reports that no more elements remain, collect stops pulling elements and hence the procession of the input stream stops and so do the messages from peek.

It is different with dropWhile, though. On the first request to it, it "fasts forward" through the input stream until the condition becomes false for the first time (this makes peek print a, b, c, de. The first element that any operation after it sees is de. From then on collect continues to pull element through the stream until it is reported empty, which is the case once the input stream ended with h, which leads to the rest of the output from peek.

Try it out with the following, which should result in a, b, c, de [de]. (I'm so sure, I didn't even try. ;) )

Stream.of("a", "b", "c", "de", "f", "g", "h")
                .peek(s -> System.out.print(s + ", "))
                .dropWhile(s -> s.length() <= 1)
                .findFirst();

Upvotes: 3

puhlen
puhlen

Reputation: 8529

This is expected behaviour and identical to how it works in scala, dropWhile processes the entire stream.

dropWhile is the reverse of takeWhile. takeWhile stops processing when the condition becomes false. dropWhile handles the entire stream, but only does not pass on any elements while the condition is true. Once the condition becomes false dropWhile passes on all of the remaining elements regardless of whether the condition is true or false.

Upvotes: 6

Related Questions