Suppose I have a stream of Things and I want to "enrich" them mid stream, I can use peek()
to do this, eg:
streamOfThings.peek(this::thingMutator).forEach(this::someConsumer);
Assume that mutating the Things at this point in the code is correct behaviour - for example, the thingMutator
method may set the "lastProcessed" field to the current time.
However, peek()
in most contexts means "look, but don't touch".
Is using peek()
to mutate stream elements an antipattern or ill-advised?
Edit:
The alternative, more conventional, approach would be to convert the consumer:
private void thingMutator(Thing thing) {
thing.setLastProcessed(System.currentTimeMillis());
}
to a function that returns the parameter:
private Thing thingMutator(Thing thing) {
thing.setLastProcessed(currentTimeMillis());
return thing;
}
and use map()
instead:
stream.map(this::thingMutator)...
But that introduces perfunctory code (the return
) and I'm not convinced it's clearer, because you know peek()
returns the same object, but with map()
it's not even clear at a glance that it's the same class of object.
Further, with peek()
you can have a lambda that mutates, but with map()
you have to build a train wreck. Compare:
stream.peek(t -> t.setLastProcessed(currentTimeMillis())).forEach(...)
stream.map(t -> {t.setLastProcessed(currentTimeMillis()); return t;}).forEach(...)
I think the peek()
version is clearer, and the lambda is clearly mutating, so there's no "mysterious" side effect. Similarly, if a method reference is used and the name of the method clearly implied mutation, that too is clear and obvious.
On a personal note, I don't shy away from using peek()
to mutate - I find it very convenient.
peek
with a stream that generates its elements dynamically? Does it still work, or are the changes lost? Modifying the elements of a stream sounds unreliable to me.List<Thing> list; things.stream().peek(list::add).forEach(...);
very handy. Lately. I've used it to add info for publishing:Map<Thing, Long> timestamps = ...; return things.stream().peek(t -> t.setTimestamp(timestamp.get(t))).collect(toList());
. I know there are other ways of doing this example, but I am greatly over-simplifying here. Usingpeek()
produces more compact, and more elegant code IMHO. Readability aside, this question is really about what you have raised; is it safe/reliable?peek
? I have similar question on stackoverflow and hope you could look at it and give your feedback. Thanks. stackoverflow.com/questions/47356992/…