how to remove object from stream in foreach method?
Solution 1
Streams are designed to be used in a more functional way, preferably treating your collections as immutable.
The non-streams way would be:
arrB.addAll(arrA);
arrA.clear();
However you might be using Streams so you can filter the input so it's more like:
arrB.addAll(arrA.stream().filter(x -> whatever).toList())
then remove from arrA (thanks to @Holgar for the comment).
arrA.removeIf(x -> whatever)
If your predicate is expensive, then you could partition:
Map<Boolean, XXX> lists = arrA.stream()
.collect(Collectors.partitioningBy(x -> whatever));
arrA = lists.get(false);
arrB = lists.get(true);
or make a list of the changes:
List<XXX> toMove = arrA.stream().filter(x->whatever).toList();
arrA.removeAll(toMove);
arrB.addAll(toMove);
Solution 2
As the others have mentioned, this is not possible with foreach
- as it is impossible with the for (A a: arrA)
loop to remove elements.
In my opinion, the cleanest solution is to use a plain for while
with iterators - iterators allow you to remove elements while iterating (as long as the collection supports that).
Iterator<A> it = arrA.iterator()
while (it.hasNext()) {
A a = it.next();
if (!check(a))
continue;
arrB.add(a);
it.remove();
}
This also saves you from copying/cloning arrA
.
Solution 3
I don't think you can remove from arrA while you iterate over it.
You can get around this by wrapping it in a new ArrayList<>();
new ArrayList<>(arrA).stream().foreach(c -> {arrB.add(c); arrA.remove(c);});
Solution 4
i guess it's because length of array is decreased after each remove() call and the counter of iterations is increased
Right. the for-each-loop is just like a normal for-loop, but easier to write and read. You can think of it as syntactic sugar. Internally it will either use an Iterator or array indices. The forEach
method of streams is a more fancy version of it that allows parallel execution and functional coding style, but has its own drawbacks.
As with any indexed loop, removing an element while looping breaks the loop. Consider having three elements with indices 0, 1, and 2. When you remove element 0 in the first iteration, the list items will shift one up and the next iteration you'll have elements 0 (previously 1) and 1 (previously 2). Your loop variable now points to 1, so it skips the actually next item. When it gets to index 2 the loop you're working on only has one item left (you removed two), which throws an error because the index is out of bounds.
Possible solutions:
- Use the
List
methods for cloning and clearing lists. - Do it with two loops if you really need to call the methods on each single item.
Comments
-
Akka Jaworek over 3 years
i have to arrays:
arrA
andarrB
.arrA
andarrB
are Lists of objectss of diffrent types andadd
function converts objectsA
to objectsB
. I want to add each object from arrA to arrB and remove that object from arrA. Im trying to do this by stream:arrA.stream().foreach(c -> {arrB.add(c); arrA.remove(c);});
when i execute this, two things are happening:
- not all objects are passed from arrA to arrB.
- after few iterations null pointer exception is thrown.
i gues it's because length of array is decreased after each
remove()
call and the counter of iterations is increased (only objects under odd indexes are passed toarrB
)Now i could solve this by copying array in one stream call and then remove objects in second stream call but this doesnt seem correct for me.
What would be proper solution to this problem?
EDIT. Additional information: in real implementation this list if previously filtered
arrA.stream().filter(some condition).foreach(c -> {arrB.add(c); arrA.remove(c);});
and its called few times to add elements meeting diffrent conditions to diffrent lists (
arrC, arrD
etc.) but each object can be only on one list