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?
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"))
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
.
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());
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"));
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());
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-
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);
}