오늘 일하는데 hashmap에서 key들을 받아서 map의 entry를 지우는 코드가 굉장히 지저분했다.
iterator로 entry를 받아서 조건에 맞는다면 remove.
그런데 이런 remove가 지저분하게 여러개.

이 코드를 고치기 위해 방법을 찾다가 재밌는 코드를 발견했다.

remove multiple keys from map efficiently

효율적으로 그리고 깔끔하게 key들로 map을 지워내기.

map.keySet().remove(key);
map.keySet().removeIf(key -> key.startsWith(prefix));

keySet은 map과 연결되어 keySet에서만 지워도 map이 지워진다는 것이다. 흥미롭다. 어떻게 이게 가능할까.

HashMap과 HashMap.keySet 코드 분석


public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {

    public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
    }

    final class KeySet extends AbstractSet<K> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        public final Iterator<K> iterator()     { return new KeyIterator(); }
        public final boolean contains(Object o) { return containsKey(o); }
        public final boolean remove(Object key) {
            return removeNode(hash(key), key, null, false, true) != null;
        }
        public final Spliterator<K> spliterator() {
            return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super K> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (Node<K,V> e : tab) {
                    for (; e != null; e = e.next)
                        action.accept(e.key);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }
}

HashMap 코드 내부에 위치한 keySet 코드이다.
흥미로운건 size, iterator, contains 등 모든 반환 값은 HashMap의 함수나 값을 쓰고 있다는 것.
그리고 HashMap.this.clear() 와 같은 코드.

확인할 포인트:

  • 우선 keySet은 HashMap과 별개라고 생각했는데 keySet이 생성된 이후에도 HashMap을 자유롭게 접근할 수 있다는 점에서 놀랐다. 이게 static도 아닌데 접근이 되서 놀랐는데 덕분에 java에 대한 이해도가 늘었다.
  • HashMap.this 코드가 이해가 안됐다. static도 아니고 이렇게 쓴다니. 이거에 대해선 아래에 다룬다.
  • inner class를 가장 효율적으로 짜는 아주 좋은 예시 코드인 것 같다. 가끔 개발에 쓸 수 있을 것 같다.

java parent.this 하기

HashMap.this에 대해 이해가 되지 않아서 찾아봤다.
keySet이 HashMap의 값을 사용할 수 있는 것을 아우르는 중요한 개념이 있다.

non static inner class는 outer class에 대한 참조를 갖는다.
그리고 outer class name과 this를 함께 사용하면 이 참조를 얻을 수 있다.

요긴하게 쓰일 수 있는 개념이자 팁.
좀 더 이해하기 쉬운 예시를 첨부한다.

public class Outer {
     class Inner {
         public Inner inner() {
             return this;
         }
 
         public Outer outer() {
             return Outer.this;
         }
     }
 }

결론

그래서 keySet을 사용해서 하는 모든 remove 연산은 실제 hashMap에 영향을 미친다.
이게 미치는 과정이 흥미롭고 배울점이 많다.

reference

multiple keys로 map remove 하기

  • https://stackoverflow.com/questions/17675804/remove-multiple-keys-from-map-in-efficient-way

hashmap.this에 대한 설명

  • https://stackoverflow.com/questions/16999611/hashmap-this-clear-what-does-this-mean-how-does-this-work