19

How can I call the setter in chain of Stream without using forEach()?

List<Foo> newFoos = foos.stream() .filter(foo -> Foo::isBlue) .map(foo -> foo.setTitle("Some value")) //I am unable to use this because also changing the data type into Object .collect(Collectors.toList()); 

3 Answers 3

17

Use peek method like this. It does not affect stream.

 List<Foo> newFoos = foos.stream() .filter(Foo::isBlue) .peek(foo -> foo.setTitle("Some value")) .collect(Collectors.toList()); 
Sign up to request clarification or add additional context in comments.

5 Comments

@richersoon Note still that this goes against the contract of the peek operation that requires a non-interfering action on the Stream element.
@Tunaki, non-interfering action means that action does not make structural modifications to the source (for example, does not add new element to the foos collection). It's perfectly ok to modify the element itself. After all, even forEach accepts non-interfering consumer, by contract. See non-interference.
@TagirValeev is right that this is not interference, but Tunaki is also right that this is an abuse of the peek() method, and may produce surprising results. If you want to mutate, use forEach(). If that means you need two pipelines, so be it. The goal is not to cram as much stuff into a pipeline as possible; it is to clearly express what is going on.
The practical consequence is that peek is not guaranteed to see every stream element. It’s action is performed for elements as they are encountered during the terminal operation specific processing and may be subject to stream pipeline optimizations. You may get surprising results…
According to its JavaDocs, java.util.Stream.peek() “exists mainly to support debugging” purposes. Although this does not mean that using it for other purposes is discouraged, relying on peek() without careful consideration can lead to error-prone code such as: If the stream pipeline does not include a terminal operation, no elements will be consumed and the peek() action will not be invoked at all.
9

Don't do that.

If you use a functional pattern, do not bring any imperative/procedural stuff in it.

The purpose of map is to transform an object into another object. It's meant to be purely functional (side-effect free) by design. Do not violate its functional meaning.

Use forEach instead:

List<Foo> newFoos = foos.stream() .filter(foo -> Foo::isBlue) .collect(toList()); newFoos.forEach(foo -> foo.setTitle("Some value")); 

You have nothing to lose here. Even the number of lines remains the same (if you concerned about that). But the code is much more readable and safe.

If you need two pipelines instead of one, then go ahead. Do not try to make the code shorter. The code length in this case has nothing to do with safety and maintainability.

Comments

5

forEach seems like a more suited tool for the job, but if you don't want to use it you could always define an anonymous multi-line lambda:

List<Foo> foos = foos.stream() .filter(foo -> Foo::isBlue) .map(foo -> { foo.setTitle("Some value"); return foo; }) .collect(Collectors.toList()); 

3 Comments

Don’t abuse .map that way, you are going to shoot yourself in the foot…
@Holger, could you elaborate on this? Why is this a bad idea?
@thathashd because you have no control about whether any when this function will be evaluated. It happens to work with a sequential stream when the terminal operation is collect, but the smallest change can break it. The comments made at this answer about peek by Brian Goetz and me apply to such a side effect in map as well. To some degree, also user12057507’s comment applies, except the peek and map have different purposes, but neither’s purpose includes such an action.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.