44

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?

2
  • 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. Commented Aug 22, 2017 at 0:15
  • 4
    Quick answer for those who came here by googling "forEach then collect": use peek. Commented Jun 7, 2018 at 8:08

8 Answers 8

49

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"))
Sign up to request clarification or add additional context in comments.

8 Comments

According to JavaDoc, peek is should be used for debugging: docs.oracle.com/javase/8/docs/api/java/util/stream/… see also stackoverflow.com/questions/33635717/…
@user140547 according to streams semantics, mutating side effects should not be performed at all but this is not always the case
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 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
In JDK 9, the specification of peek() was clarified to make it clear that the implementation may optimize away peek().
|
22

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.

3 Comments

Will this work if you need in order processing of the stream?
@alex exactly this example : yes.
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.
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());

3 Comments

When using "map" with the code above, then debugger says: "Cannot infer type argument(s) for <R> map(Function<? super T,? extends R>)". Using "peek" instead, solves this issue. So I guess, it is better to use "peek" for such cases.
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.
Need slight edit, sorry. Because function needs to return a value for a map... Thanks @nimo23, I wrote to fast.
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"));

4 Comments

peek is for side-effects
@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 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
@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.
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());

1 Comment

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
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-

1 Comment

The documentation you provided explicitly states to only ever use this method for debugging. I would not use it for side effects.
1

Use .peek() instead of .forEach()

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

Comments

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);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.