Object#hashCode()とIdentityHashMapとEclipseLink

ちょっと気になる記事が二つほど。
-

EclipseLinkではjava.util.IdentityHashMapを利用しているようですが、java.util.IdentityHashMapではjava.lang.Object#hashCode()を利用して同一性を確保しています。

このクラスは、システム識別ハッシュ関数 (System.identityHashCode(Object)) が複数のバケットに要素を適切に分散させると仮定して、基本オペレーション (get および put) に一定時間のパフォーマンスを提供します。

hashCodeがrandamな値を利用しているという時点で、違うオブジェクトが同じhashCodeを持ってしまう可能性があるってことか。
その場合、実は同じオブジェクトとして扱われてしまうんだっけ?

と、思って動作を調べてみました。

import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;

public class ITest {

  public static void main(String[] args) {
    Map<Integer, Integer> hashCodes = new HashMap<Integer, Integer>(10000000);
    Map<Object, Object> os = new IdentityHashMap<Object, Object>(10000000);
    for (int i = 0; i < 10000000; i++) {
      Object o = new Object() {
        // 念のためequalsメソッドをオーバーライドしてみる
        @Override
        public boolean equals(Object o) {
          return this.hashCode() == o.hashCode();
        }
      };
      hashCodes.put(o.hashCode(), o.hashCode());
      os.put(o, o);
    }

    System.out.println("hashCode:" + hashCodes.size());
    System.out.println("objects :" + os.size());
  }
}

結果

hashCode:8663937
objects :10000000

おー、きちんと違うものとして扱われている。と思ってHashMapとIdentityHashMapの実装を比べてみると、put時の同一性確認の動作が違うみたい。

・HashMap

  public V put(K key, V value) {
    if (key == null)
      return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
      Object k;
      if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
        V oldValue = e.value;
        e.value = value;
        e.recordAccess(this);
        return oldValue;
      }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
  }

・IdentityHashMap

  public V put(K key, V value) {
    Object k = maskNull(key);
    Object[] tab = table;
    int len = tab.length;
    int i = hash(k, len);

    Object item;
    while ( (item = tab[i]) != null) {
      if (item == k) { //equalsでの比較がない
        V oldValue = (V) tab[i + 1];
        tab[i + 1] = value;
        return oldValue;
      }
      i = nextKeyIndex(i, len);
    }

    modCount++;
    tab[i] = k;
    tab[i + 1] = value;
    if (++size >= threshold)
      resize(len); // len == 2 * current capacity.
    return null;
  }

"=="での比較ってhashCodeだけで判断されているわけではないのね。
よくよく考えたら当然なお話な気もしてきましたので、コアな場所はあまり意識せずに使ってるので忘れやすいというお話でした。