Barcelona
Barcelona

Reputation: 2132

How peek() and allMatch() works together in Java 8 Stream API

I found a quiz about Java 8 Stream API of peek method as below

Arrays.asList("Fred", "Jim", "Sheila")
      .stream()
      .peek(System.out::println)
      .allMatch(s -> s.startsWith("F"));

The output is

Fred
Jim

I am confused how this stream works? My expected result should be

Fred
Jim
Sheila

The peek() method is an intermediate operation and it processes each element in Stream. Can anyone explain me this.

Upvotes: 9

Views: 1728

Answers (4)

Sandeep Tiwari
Sandeep Tiwari

Reputation: 2072

As per Java Doc Of allMatch():

Returns whether all elements of this stream match the provided predicate. May not evaluate the predicate on all elements if not necessary for determining the result. If the stream is empty then {@code true} is returned and the predicate is not evaluated.

@apiNote

This method evaluates the universal quantification of the predicate over the elements of the stream (for all x P(x)). If the stream is empty, the quantification is said to be vacuously satisfied and is always {@code true} (regardless of P(x)).

predicate to apply to elements of this stream @return {@code true} if either all elements of the stream match the provided predicate or the stream is empty, otherwise {@code false}

In your case:

1-

p(x) : s -> s.startsWith("F")

X : "Fred"

result : X P(X) = true

2-

p(x) : s -> s.startsWith("F")

X : "Jim"

result : X P(X) = false

No further evaluation will take place, because X P(X) = false

boolean result = Arrays.asList("Fred", "Finda", "Fish")
            .stream()
            .peek(System.out::println)
            .allMatch(s -> s.startsWith("F"));
    System.out.println("Result "+result);

Output is :

Fred
Finda
Fish
Result true

Here stream processed completely because xP(x) = true from each element

Upvotes: 1

ernest_k
ernest_k

Reputation: 45309

It's a stream optimization known as short-circuiting. Essentially, what happens is that allMatch prevents the execution of unnecessary intermediate operations on the stream, because there is no point in performing them when the final result is known.

It's as though this happened:

take"Fred"
peek("Fred")
evaluate("Fred".startsWith("F"))
decide whether the result of allMatch() is known for sure: Not yet

take"Jim"
peek("Jim")
evaluate("Jim".startsWith("F"))
decide whether the result of allMatch() is known for sure: Yes

When "Jim".startsWith("F") is evaluated, the result of allMatch(s -> s.startsWith("F")) is known for certain. It doesn't matter what values come in the pipeline after "Jim", we know that all values start with "F" is false

This is not specific to the peek/allMatch combination, there are multiple intermediate and terminal short-circuiting operations. java.util.stream package's docs state:

Further, some operations are deemed short-circuiting operations. An intermediate operation is short-circuiting if, when presented with infinite input, it may produce a finite stream as a result. A terminal operation is short-circuiting if, when presented with infinite input, it may terminate in finite time. Having a short-circuiting operation in the pipeline is a necessary, but not sufficient, condition for the processing of an infinite stream to terminate normally in finite time.

Extend this to finite streams, and short-circuiting operations obviate the execution of unnecessary pipeline steps, as in the case of your example.

Upvotes: 9

WJS
WJS

Reputation: 40034

Arrays.asList("Fred", "Jim", "Sheila")
      .stream()
      .peek(System.out::println)
      .allMatch(s -> s.startsWith("F"));
  • First time thru, Fred is printed. It matches so
  • Second time thru, Jim is printed. It doesn't match so allMatch terminates because "All didn't match"
  • So the last item was not consumed from the stream.

Upvotes: 4

kaya3
kaya3

Reputation: 51034

The docs for the peek method say (emphasis mine):

Returns a stream consisting of the elements of this stream, additionally performing the provided action on each element as elements are consumed from the resulting stream.

So in this case, peek doesn't see "Sheila" because that value is not consumed from the stream. As soon as "Jim" was consumed, the result of .allMatch(s -> s.startsWith("F")) is already known to be false, so there is no need to consume any more elements from the stream.

Upvotes: 2

Related Questions