iterating over and removing from a map [duplicate]
Asked Answered
J

12

305

I was doing:

for (Object key : map.keySet())
    if (something)
        map.remove(key);

which threw a ConcurrentModificationException, so i changed it to:

for (Object key : new ArrayList<Object>(map.keySet()))
    if (something)
        map.remove(key);

this, and any other procedures that modify the map are in synchronized blocks.

is there a better solution?

Jonis answered 10/12, 2009 at 23:36 Comment(2)
If this method and the other method that modify the map are in synchronized blocks, I don't see why you have to do anything? Maybe I'm not understanding your question completely? Can ou please post the rest of the code?Brinkley
@Raedwald, this question and it's accepted answer are more succinct than the other IMO.Jonis
A
354

As of Java 8 you could do this as follows:

map.entrySet().removeIf(e -> <boolean expression>);

Oracle Docs: entrySet()

The set is backed by the map, so changes to the map are reflected in the set, and vice-versa

Angieangil answered 21/3, 2015 at 20:40 Comment(3)
Note that map.values() and map.keySet() also support removeIf().Aurum
What is the behaviour of removeIf on map.values()? it removes all the key->val elements pointing to such value?Palatinate
In removeIf() we are giving condition, it will remove all the matched recordsRatfink
O
411

Here is a code sample to use the iterator in a for loop to remove the entry.

Map<String, String> map = new HashMap<String, String>() {
  {
    put("test", "test123");
    put("test2", "test456");
  }
};

for(Iterator<Map.Entry<String, String>> it = map.entrySet().iterator(); it.hasNext(); ) {
    Map.Entry<String, String> entry = it.next();
    if(entry.getKey().equals("test")) {
        it.remove();
    }
}
Oversight answered 10/12, 2009 at 23:41 Comment(7)
So, you do: it.remove() and never collection.remove(key) inside a loop. It's great!Ingredient
This answer is good for code before Java 8, but elron's answer is preferable if you are using Java 8. https://mcmap.net/q/36532/-iterating-over-and-removing-from-a-map-duplicateDapple
This works great in Android too.Remember
If an item were added to this Map while it was being recursed to remove items wouldn't it still throw a ConcurrentModificationException?Fia
Hashmap has a fail-fast iterator. Then why does it.remove() not throw a ConcurrentModificationException ?Reckford
Since Java 8, you can also use this instead: map.entrySet().removeIf(entry -> entry.getValue().equals("test"));Flaring
@Dapple This is still better if we have some other logic as well inside the for loop, which was my case.Posey
A
354

As of Java 8 you could do this as follows:

map.entrySet().removeIf(e -> <boolean expression>);

Oracle Docs: entrySet()

The set is backed by the map, so changes to the map are reflected in the set, and vice-versa

Angieangil answered 21/3, 2015 at 20:40 Comment(3)
Note that map.values() and map.keySet() also support removeIf().Aurum
What is the behaviour of removeIf on map.values()? it removes all the key->val elements pointing to such value?Palatinate
In removeIf() we are giving condition, it will remove all the matched recordsRatfink
S
110

Use a real iterator.

Iterator<Object> it = map.keySet().iterator();

while (it.hasNext())
{
  it.next();
  if (something)
    it.remove();
 }

Actually, you might need to iterate over the entrySet() instead of the keySet() to make that work.

Shornick answered 10/12, 2009 at 23:39 Comment(10)
This does seem a bit more elegant than my solution of iterating over the entry set. Its not very obvious though that removing from the key set removes things from the map (i.e. the key set can be a copy)Oversight
Sorry to double comment. I have confirmed that removing from the key set indeed removes from the map, although not as obvious as removing from the entry set.Oversight
Iterating over the key set is the way to do it.Bloem
(Although to match the question, it should be Iterator<Integer>.)Bloem
Tom, in the first loop he's using "for (Object key:", so that's why I used an Object iterator.Shornick
Iterator.next() or in this case it.next() must be invoked before the call to remove otherwise there will be an IllegalStateException.Anagrammatize
@broiyan, you are correct, I forgot that.Shornick
For the record, this also works for the map values: Iterator it = map.values().iterator(), then it.remove() will remove it entry.Dictaphone
Hashmap has a fail-fast iterator. Then why does it.remove() not throw a ConcurrentModificationException ?Reckford
@Reckford A fail-fast iterator fails when something else modifies the container concurrently. You are allowed to use the iterator as intended. In this answer, nothing is modifying the key Set except for the iterator, and that is translated through to the Map owning that Set as well. Only one operation per container is occurring at a time; no concurrency.Nave
D
50

is there a better solution?

Well, there is, definitely, a better way to do so in a single statement, but that depends on the condition based on which elements are removed.

For eg: remove all those elements where value is test, then use below:

map.values().removeAll(Collections.singleton("test"));

UPDATE It can be done in a single line using Lambda expression in Java 8.

map.entrySet().removeIf(e-> <boolean expression> );

I know this question is way too old, but there isn't any harm in updating the better way to do the things :)

Dhiren answered 27/3, 2014 at 5:40 Comment(5)
It may be an old question but that really helped me. I'd never heard of Collections.singleton() and I had no idea you could remove elements from a map by calling removeAll() on values()! Thank you.Saltcellar
To remove the element which key is test: map.keySet().removeAll(Collections.singleton("test"));Grandfatherly
@MarouaneLakhal To remove the element which key is test, wouldn't you just do map.remove("test"); ?Cortex
@Cortex as stated in the question, map.remove(key) threw a ConcurrentModificationException when he was looping over the map.keySet().Grandfatherly
@MarouaneLakhal If you already know the key of an element of the map you want to remove already, why are you looping in the first place? There can only be one entry in the map with the same key.Cortex
P
25

ConcurrentHashMap

You can use java.util.concurrent.ConcurrentHashMap.

It implements ConcurrentMap (which extends the Map interface).

E.g.:

Map<Object, Content> map = new ConcurrentHashMap<Object, Content>();

for (Object key : map.keySet()) {
    if (something) {
        map.remove(key);
    }
}

This approach leaves your code untouched. Only the map type differs.

Proficiency answered 16/9, 2014 at 22:58 Comment(1)
The problem with this approach is that a ConcurrentHashMap does not allow "null" as value (or key), so if by any chance you are dealing with a map containing a null, you could not use this approach.Indiscernible
T
9

Java 8 support a more declarative approach to iteration, in that we specify the result we want rather than how to compute it. Benefits of the new approach are that it can be more readable, less error prone.

public static void mapRemove() {

    Map<Integer, String> map = new HashMap<Integer, String>() {
        {
            put(1, "one");
            put(2, "two");
            put(3, "three");
        }
    };

    map.forEach( (key, value) -> { 
        System.out.println( "Key: " + key + "\t" + " Value: " + value );  
    }); 

    map.keySet().removeIf(e->(e>2)); // <-- remove here

    System.out.println("After removing element");

    map.forEach( (key, value) -> { 
        System.out.println( "Key: " + key + "\t" + " Value: " + value ); 
    });
}

And result is as follows:

Key: 1   Value: one
Key: 2   Value: two
Key: 3   Value: three
After removing element
Key: 1   Value: one
Key: 2   Value: two
Themis answered 28/1, 2016 at 10:38 Comment(0)
O
5

You have to use Iterator to safely remove element while traversing a map.

Oversupply answered 10/12, 2009 at 23:39 Comment(0)
A
4

I agree with Paul Tomblin. I usually use the keyset's iterator, and then base my condition off the value for that key:

Iterator<Integer> it = map.keySet().iterator();
while(it.hasNext()) {
    Integer key = it.next();
    Object val = map.get(key);
    if (val.shouldBeRemoved()) {
        it.remove();
    }
}
Aubigny answered 11/12, 2009 at 1:59 Comment(1)
You should use the entrySet instead of using the keySet and doing a get every time. FindBugs even has a detector for this: findbugs.sourceforge.net/…Deraign
A
2

An alternative, more verbose way

List<SomeObject> toRemove = new ArrayList<SomeObject>();
for (SomeObject key: map.keySet()) {
    if (something) {
        toRemove.add(key);
    }
}

for (SomeObject key: toRemove) {
    map.remove(key);
}
Artieartifact answered 11/12, 2009 at 0:21 Comment(0)
B
2

And this should work as well..

ConcurrentMap<Integer, String> running = ... create and populate map

Set<Entry<Integer, String>> set = running.entrySet();    

for (Entry<Integer, String> entry : set)
{ 
  if (entry.getKey()>600000)
  {
    set.remove(entry.getKey());    
  }
}
Broddy answered 6/6, 2014 at 10:7 Comment(1)
Shouldn't it be: running.remove(entry.getKey());Glanti
H
1

Maybe you can iterate over the map looking for the keys to remove and storing them in a separate collection. Then remove the collection of keys from the map. Modifying the map while iterating is usually frowned upon. This idea may be suspect if the map is very large.

Heurlin answered 10/12, 2009 at 23:42 Comment(1)
If you can use a for-each it's definitely the more elegant solutionCordiacordial
E
-2
Set s=map.entrySet();
Iterator iter = s.iterator();

while (iter.hasNext()) {
    Map.Entry entry =(Map.Entry)iter.next();

    if("value you need to remove".equals(entry.getKey())) {
         map.remove();
    }
}
Ebbie answered 4/4, 2015 at 20:10 Comment(2)
There should be some explanation as well.Iams
i think map.remove will thow concurrentModificationExceptiomDennet

© 2022 - 2024 — McMap. All rights reserved.