tsolakp
tsolakp

Reputation: 5948

Possible side effect of Stream.peek changing state and why not use it like this

A solution that I came up on another Stackoverflow question that is using Stream.peek operation works but still seems like is not right because it mutates state in the Stream.peek method.

While researching (here and here) on Stream.peek usage whether it is ok to mutate state I am still not fully convinced that Stream.peek should not mutate state (including state of collection that is source of the Stream).

Here is what Javadoc says:

This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline:

And then:

Parameters: action - a non-interfering action to perform on the elements as they are consumed from the stream.

For well-behaved non-interfering stream sources, the source can be modified before the terminal operation commences and those modifications will be reflected in the covered elements.All the streams returned from JDK collections, and most other JDK classes, are well-behaved in this manner.

Seems like non-interfering action does includes changing the state of collection in the stream.

Here is the code that uses Stream.peek.

Map< String, List<Test> > userTests = new HashMap<>();

Map< String, List<Test> > filtered  = userTests.entrySet().stream()
        .peek( e -> e.setValue( modifyListAndReturnIt( e.getValue() ) ) )
        .filter( e -> !e.getValue().isEmpty() ) //check if modified list in peek has been emptied
        .collect( Collectors.toMap(p -> p.getKey(), p -> p.getValue() ) );

    public static List<Test> modifyListAndReturnIt(List<Test> list){
        if (somecondition) list.clear();
        return list;
    }

1) Can the above code have any side effect?

2) Why not use peek in such a way. The Javadoc does not seem to not allow it?

Upvotes: 3

Views: 2961

Answers (1)

Eugene
Eugene

Reputation: 121078

What you seem to do looks harmless as Brian Goetz states in comment here.

Now the problem with peek is that if you do side effects inside it - you would expect these side effects to actually happen. So, suppose you would want to alter some property of some object like this:

myUserList.stream()
          .peek(u -> u.upperCaseName())
          .count()

In java-8 your peek would be indeed called, in 9 - it is not - there is no need to call peek here since the size can be computed without it anyway.

While being on the same path, imagine that your terminal operation is a short-circuit one, like findFirst or findAny - you are not going to process all elements of the source - you might get just a few of them through the pipeline.

Things might get even stranger if your peek would rely on a encounter order even if your terminal operation would not be a short-circuit one. The intermediate operations for parallel processing do not have an encounter order, while the terminal ones - do. Imagine the surprises you might be facing.

Upvotes: 4

Related Questions