29

I want to remove all items from someMap which keys are not present in someList.
Take a look into my code:

someMap.keySet()
    .stream()
    .filter(v -> !someList.contains(v))
    .forEach(someMap::remove);

I receive java.util.ConcurrentModificationException.
Why I faced this Exception given that the stream is not parallel?
What is the most elegant way to do this?

4 Answers 4

47

@Eran already explained how to solve this problem better. I will explain why ConcurrentModificationException occurs.

The ConcurrentModificationException occurs because you are modifying the stream source. Your Map is likely to be HashMap or TreeMap or other non-concurrent map. Let's assume it's a HashMap. Every stream is backed by Spliterator. If spliterator has no IMMUTABLE and CONCURRENT characteristics, then, as documentation says:

After binding a Spliterator should, on a best-effort basis, throw ConcurrentModificationException if structural interference is detected. Spliterators that do this are called fail-fast.

So the HashMap.keySet().spliterator() is not IMMUTABLE (because this Set can be modified) and not CONCURRENT (concurrent updates are unsafe for HashMap). So it just detects the concurrent changes and throws a ConcurrentModificationException as spliterator documentation prescribes.

Also it worth citing the HashMap documentation:

The iterators returned by all of this class's "collection view methods" are fail-fast: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove method, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.

While it says about iterators only, I believe it's the same for spliterators.

Sign up to request clarification or add additional context in comments.

2 Comments

I think this is the best answer, but you may edit it to add solution that @Eran mentioned. It would be 100% satisfying for anyone having the same problem in the future.
@MariuszJaskółka, Eran answer is also here and other people will likely to see it as well. It's correct and I upvoted it. I can add a reference to his solution.
17

You don't need the Stream API for that. Use retainAll on the keySet. Any changes on the Set returned by keySet() are reflected in the original Map.

someMap.keySet().retainAll(someList);

1 Comment

OK, it is good answer for my second question. But I still don't know why java.util.ConcurrentModificationException occures.
12

Your stream call is (logically) doing the same as:

for (K k : someMap.keySet()) {
    if (!someList.contains(k)) {
        someMap.remove(k);
    }
}

If you run this, you will find it throws ConcurrentModificationException, because it is modifying the map at the same time as you're iterating over it. If you have a look at the docs, you'll notice the following:

Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception. For example, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.

This is what you are doing, the map implementation you're using evidently has fail-fast iterators, therefore this exception is being thrown.

One possible alternative is to remove the items using the iterator directly:

for (Iterator<K> ks = someMap.keySet().iterator(); ks.hasNext(); ) {
    K next = ks.next();
    if (!someList.contains(k)) {
        ks.remove();
    }
}

Comments

11

Later answer, but you could insert a collector into your pipeline so that forEach is operating on a Set which holds a copy of the keys:

someMap.keySet()
    .stream()
    .filter(v -> !someList.contains(v))
    .collect(Collectors.toSet())
    .forEach(someMap::remove);

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.