18

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());
Alexis C.
  • 91,686
  • 21
  • 171
  • 177
richersoon
  • 4,682
  • 13
  • 44
  • 74

3 Answers3

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());
  • 1
    @richersoon Note still that this goes against the contract of the [`peek`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#peek-java.util.function.Consumer-) operation that requires a _non-interfering_ action on the Stream element. – Tunaki Feb 13 '16 at 11:25
  • 5
    @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](https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#NonInterference). – Tagir Valeev Feb 13 '16 at 12:06
  • 8
    @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. – Brian Goetz Feb 14 '16 at 14:56
  • 3
    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… – Holger Feb 15 '16 at 10:05
  • 1
    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. –  Oct 14 '19 at 06:46
8

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.

ETO
  • 6,970
  • 1
  • 20
  • 37
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()); 
Mureinik
  • 297,002
  • 52
  • 306
  • 350
  • 13
    Don’t abuse `.map` that way, you are going to shoot yourself in the foot… – Holger Feb 15 '16 at 10:06
  • @Holger, could you elaborate on this? Why is this a bad idea? – thathashd Apr 30 '21 at 07:47
  • 1
    @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](https://stackoverflow.com/a/35377979/2711488) 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. – Holger Apr 30 '21 at 08:22