how to remove object from stream in foreach method?

33,213

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.
Share:
33,213
Akka Jaworek
Author by

Akka Jaworek

i code

Updated on August 31, 2020

Comments

  • Akka Jaworek
    Akka Jaworek over 3 years

    i have to arrays: arrA and arrB. arrA and arrB are Lists of objectss of diffrent types and add function converts objects A to objects B. 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:

    1. not all objects are passed from arrA to arrB.
    2. 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 to arrB)

    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