43

I want to achieve something like this:

items.stream()
    .filter(s-> s.contains("B"))
    .forEach(s-> s.setState("ok"))
.collect(Collectors.toList());

filter, then change a property from the filtered result, then collect the result to a list. However, the debugger says:

Cannot invoke collect(Collectors.toList()) on the primitive type void.

Do I need 2 streams for that?

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
nimo23
  • 5,170
  • 10
  • 46
  • 75
  • 1
    The right thing to do here is to collect the selected entries into a target collection, and then do `results.forEach(...)` to perform your side-effect. – Brian Goetz Aug 22 '17 at 00:15
  • 3
    Quick answer for those who came here by googling "forEach then collect": use `peek`. – izogfif Jun 07 '18 at 08:08

8 Answers8

46

The forEach is designed to be a terminal operation and yes - you can't do anything after you call it.

The idiomatic way would be to apply a transformation first and then collect() everything to the desired data structure.

The transformation can be performed using map which is designed for non-mutating operations.

If you are performing a non-mutating operation:

 items.stream()
   .filter(s -> s.contains("B"))
   .map(s -> s.withState("ok"))
   .collect(Collectors.toList());

where withState is a method that returns a copy of the original object including the provided change.


If you are performing a side effect:

items.stream()
  .filter(s -> s.contains("B"))
  .collect(Collectors.toList());

items.forEach(s -> s.setState("ok"))
Grzegorz Piwowarek
  • 13,172
  • 8
  • 62
  • 93
  • 2
    According to JavaDoc, peek is should be used for debugging: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#peek-java.util.function.Consumer- see also https://stackoverflow.com/questions/33635717/in-java-streams-is-peek-really-only-for-debugging – user140547 Aug 21 '17 at 10:34
  • 1
    @user140547 according to streams semantics, mutating side effects should not be performed `at all` but this is not always the case – Grzegorz Piwowarek Aug 21 '17 at 10:36
  • Well, `forEach()` also performs a side-effect. But `peek()` is more fragile as it is possible that it is optimized away. Although `peek()` will probably be called for every element when collecting to a list, from a maintainability perspective the code may break if someone unaware of this feature changes the terminal operation to `count()`, `findFirst()` etc. – user140547 Aug 21 '17 at 19:15
  • @user140547 well, the contracted behavior can't change because that would break the backward compatibility even when the method exists "mainly for debugging" and "people unaware of this feature" should read the docs first – Grzegorz Piwowarek Aug 21 '17 at 19:23
  • 2
    In JDK 9, the specification of `peek()` was clarified to make it clear that the implementation may optimize away `peek()`. – Brian Goetz Aug 22 '17 at 00:10
  • @BrianGoetz thanks for the clarification, is it possible that peek() will NOT touch an element that went through the stream? – Grzegorz Piwowarek Aug 22 '17 at 06:53
  • @BrianGoetz that's interesting. How would that decision be taken? Since it's used for debugging quite a lot - I always expect it to *happen*. I do agree that `map` for example might get optimized away since the result might not be used, but `peek`... – Eugene Aug 22 '17 at 08:20
  • @Eugene I think you essentially want `peek()` to work as the OP asks -- "like `forEach()`, but an intermediate op", so you can freely do side-effects in streams. But that's not what it's there for. Let go of that idea, and you're fine. – Brian Goetz Aug 22 '17 at 13:13
21

Replace forEach with map.

 items.stream()
      .filter(s-> s.contains("B"))
      .map(s-> {s.setState("ok");return s;})
      .collect(Collectors.toList());

forEach and collect are both terminal operations - Streams must have just one. Anything that returns a Stream<T> is a intermediate operation, anything other is a terminal operation.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • Will this work if you need in order processing of the stream? – alex Jun 03 '20 at 18:53
  • @alex exactly this example : yes. – Eugene Jun 03 '20 at 23:37
  • Sonar will complain this, as saying Style - Method returns modified parameter This method appears to modify a parameter, and then return this parameter as the method's return value. This will be confusing to callers of this method, as it won't be apparent that the 'original' passed-in parameter will be changed as well. If the purpose of this method is to change the parameter, it would be more clear to change the method to have a void return value. If a return type is required due to interface or superclass contract, perhaps a clone of the parameter should be made. – SebastianX Apr 16 '21 at 08:56
6

Resist the urge to use side-effects from inside the stream without a very good reason. Make the new list and then apply the changes:

List<MyObj> toProcess = items.stream()
    .filter(s -> s.contains("B"))
    .collect(toList());

toProcess.forEach(s -> s.setState("ok"));
Grzegorz Piwowarek
  • 13,172
  • 8
  • 62
  • 93
Misha
  • 27,433
  • 6
  • 62
  • 78
  • `peek` is for side-effects – Grzegorz Piwowarek Aug 21 '17 at 09:30
  • @GrzegorzPiwowarek ...but it is somewhat tricky to predict on which elements it will be invoked, and this will not get easier with java 9 (which allows more optimizations by stream implementations). You'd need to check upstream as well as downstream operations (`filter`, `distinct`, short-circuiting terminal operations like `count()`, `anyMatch()`, `findFirst()`, ...) to be sure. For simple cases like this one that is probably ok, but in general it is better to avoid relying on it. – Hulk Aug 22 '17 at 13:26
  • @Hulk I am aware of that - I hope there is no way peek() call can be skipped when terminating stream using methods that should visit all elements (like the one you mentioned above). It's a shame it needs to be done in two steps like this – Grzegorz Piwowarek Aug 22 '17 at 13:47
  • @GrzegorzPiwowarek well `count` and `findFirst` are explicitly mentioned in drafts for the JavaDocs ("In cases where the stream implementation is able to optimize away the production of some or all the elements (such as with short-circuiting operations like findFirst, or in the example described in count()), the action will not be invoked for those elements."), in a few days we will know for sure what the wording in the final release will be. – Hulk Aug 22 '17 at 13:52
6

forEach is a terminal operation, means that it produces non-stream result. forEach doesn't produces anything and collect returns a collection. What you need is a stream operation that modifies elements for your needs. This operation is map which lets you specify a function to be applied to each element of the input stream and produces a transformed stream of elements. So you need something like:

items.stream()
     .filter (s -> s.contains("B"))
     .map    (s -> { s.setState("ok"); return s; }) // need to return a value here
     .collect(Collectors.toList());

An alternative is to use peek whose intention is to apply a function to each element traversing (but its main purpose is for debugging):

items.stream()
     .filter (s -> s.contains("B"))
     .peek   (s -> s.setState("ok")) // no need to return a value here
     .collect(Collectors.toList());
Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
  • When using "map" with the code above, then debugger says: "Cannot infer type argument(s) for map(Function super T,? extends R>)". Using "peek" instead, solves this issue. So I guess, it is better to use "peek" for such cases. – nimo23 Aug 21 '17 at 10:12
  • When using "map" one must return the instance: ".map (s -> {s.setState("ok"); return s;})". With this, it works like "peek". However, "peek" must not return value. – nimo23 Aug 21 '17 at 10:14
  • Need slight edit, sorry. Because function needs to return a value for a map... Thanks @nimo23, I wrote to fast. – Jean-Baptiste Yunès Aug 21 '17 at 10:39
4

You cannot execute two terminal operations on the same Stream.

You can set the state of the object in an intermediate operation, such as map:

List<YourClass> list = 
    items.stream()
         .filter(s-> s.contains("B"))
         .map(s-> {
                      s.setState("ok"); 
                      return s;
                  })
         .collect(Collectors.toList());
Eran
  • 387,369
  • 54
  • 702
  • 768
  • Style - Method returns modified parameter Code Smell FindBugs Contrib (Java) This method appears to modify a parameter, and then return this parameter as the method's return value. This will be confusing to callers of this method, as it won't be apparent that the 'original' passed-in parameter will be changed as well. If the purpose of this method is to change the parameter, it would be more clear to change the method to have a void return value. If a return type is required due to interface or superclass contract, perhaps a clone of the parameter should be made. -- Sonar will complain as – SebastianX Apr 16 '21 at 09:00
4
 items.stream()
      .filter(s-> s.contains("B"))
      .peek(s-> s.setState("ok"))
      .collect(Collectors.toList());

Stream peek(Consumer action) 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. This is an intermediate operation.

For parallel stream pipelines, the action may be called at whatever time and in whatever thread the element is made available by the upstream operation. If the action modifies shared state, it is responsible for providing the required synchronization.

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

 Stream.of("one", "two", "three", "four")
     .filter(e -> e.length() > 3)
     .peek(e -> System.out.println("Filtered value: " + e))
     .map(String::toUpperCase)
     .peek(e -> System.out.println("Mapped value: " + e))
     .collect(Collectors.toList());   Parameters: action - a non-interfering action to perform on the elements as they are consumed

from the stream Returns: the new stream

https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#peek-java.util.function.Consumer-

宏杰李
  • 11,820
  • 2
  • 28
  • 35
  • The documentation you provided explicitly states to only ever use this method for debugging. I would not use it for side effects. – Markus Rohlof Jan 14 '22 at 10:35
1

Use .peek() instead of .forEach()

items.stream()
    .filter(s-> s.contains("B"))
    .peek(s-> s.setState("ok"))
.collect(Collectors.toList());
Leena
  • 703
  • 1
  • 12
  • 21
0
public static void main(String[] args) {
    // Create and populate the Test List
    List<Object> objectList = new ArrayList<>();
    objectList.add("s");
    objectList.add(1);
    objectList.add(5L);
    objectList.add(7D);
    objectList.add(Boolean.TRUE);

    // Filter by some condition and collect
    List<Object> targetObjectList = 
        objectList.stream().filter(o -> o instanceof String)
        .collect(Collectors.toList());

    // Check
    targetObjectList.forEach(System.out::println);
}
Julian Kolodzey
  • 359
  • 3
  • 9