Reputation: 5708
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 typevoid
.
Do I need 2 streams for that?
Upvotes: 44
Views: 95899
Reputation: 783
Use .peek()
instead of .forEach()
items.stream()
.filter(s-> s.contains("B"))
.peek(s-> s.setState("ok"))
.collect(Collectors.toList());
Upvotes: 1
Reputation: 407
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);
}
Upvotes: 0
Reputation: 12178
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
Upvotes: 4
Reputation: 13883
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"))
Upvotes: 48
Reputation: 28183
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"));
Upvotes: 6
Reputation: 36441
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());
Upvotes: 6
Reputation: 394146
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());
Upvotes: 4
Reputation: 121078
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
.
Upvotes: 22