プログラマ38の日記

主にプログラムメモです。

Java: Map.remove ではまりました(java.util.ConcurrentModificationException)

Javaコードを書いていて、for each 内で、Mapのremoveを行いjava.util.ConcurrentModificationExceptionエラーになるのをすっかり忘れてました。

 

エラーになるコード

        HashMap<String, String> map = new HashMap<String, String>();
        
        map.put("k1", "v1");
        map.put("k2", "v2");
        map.put("k3", "v3");
        
        for(String k : map.keySet()){
            if( k.endsWith("2")){
                map.remove(k);
            }
        }

 

エラーにならないコード

        HashMap<String, String> map = new HashMap<String, String>();
        
        map.put("k1", "v1");
        map.put("k2", "v2");
        map.put("k3", "v3");
        
        for(Iterator<String> i = map.keySet().iterator();i.hasNext();){
            String k = i.next();
            if( k.endsWith("2")){
                i.remove();
            }
        }

 

Iteratorで要素を順に取得する場合は、要素の削除は Iterator.remove で行う必要がありました。(拡張for文はコンパイル時にIteraterのコードに展開されるようです)

すっかり拡張for文に慣れてしまいIteratorなんてほぼ目にしなくなっているのに、removeするときだけIteratorで回すのは面倒くさいなーと思いました。

 

removeではなく、新しいMapを作成する

処理に問題がなければ、removeした結果と同じMapを新規に作成しとけば問題ないですよね。

        HashMap<String, String> map = new HashMap<String, String>();
        
        map.put("k1", "v1");
        map.put("k2", "v2");
        map.put("k3", "v3");
        
        HashMap<String, String> newmap = new HashMap<String, String>();
        for(String k : map.keySet()){
            
            if( k.endsWith("2") == false){
                newmap.put(k, map.get(k));
            }
        }
        
        map = newmap;

さらにStreamAPIで綺麗に書けるようになってます。

    HashMap<String, String> map = new HashMap<String, String>();
    
    map.put("k1", "v1");
    map.put("k2", "v2");
    map.put("k3", "v3");
    
    map = map.entrySet().stream()
        .filter(m-> m.getKey().endsWith("2")==false)
        .collect(
          Collectors.toMap(Entry::getKey, Entry::getValue, (e1, e2) -> e1,HashMap::new)
        );

 

でもラムダ式に慣れてなくて、いちいち調べないとまともに書けないですねー。ちゃんと把握しようかな。。と思いました。