HashMapの挙動がおかしいときに確認すべきこと
HashMap便利ですよね。キーに対してオブジェクトを紐付けられるあれです。java.util.HashMapです。
結論から書くと、Comparableを実装したクラスをキーにしたら、compareToを規約どおり実装しろということです。
※規約: http://docs.oracle.com/javase/jp/8/docs/api/java/lang/Comparable.html#compareTo-T-
このオブジェクトが指定されたオブジェクトより小さい場合は負の整数、等しい場合はゼロ、大きい場合は正の整数を返します。
実装では、すべてのxとyに対してsgn(x.compareTo(y))== -sgn(y.compareTo(x))が保証されなければいけません。これは、y.compareTo(x)が例外をスローする場合はx.compareTo(y)も例外をスローすることを意味します。
実装では、順序関係が推移的であることも保証されなければいけません。(x.compareTo(y)>0 && y.compareTo(z)>0)はx.compareTo(z)>0を意味します。
さらに、すべてのzに対してx.compareTo(y)==0がsgn(x.compareTo(z))== sgn(y.compareTo(z))を意味することも保証されなければいけません。
最近、HashMapを使っていたときに、HashMapに存在するはずのオブジェクトがcontainsKeyしても引っかからないという現象に出くわしまして、解決までかなり時間を食われてしまいました。
・HashMapのキーに存在するオブジェクトをcontainsKeyに渡してもfalseが返る
・そのオブジェクトのhashCodeはHashMapに格納されている、目的のオブジェクトと一致する
・そのオブジェクトはHashMapに格納されている、目的のオブジェクトとequalsな関係にある (equals == true)
調べてみると、Java8からHashMapの内部で、Comparableを実装したクラスをキーにした場合、効率化のためにComparable#compareToが用いられるようで。
試しに、compareToで-1, 0, 1をランダムに返すクラスを作ってHashMapに格納してみたところ、containsKeyでfalseを返すようになりました。
実装では、順序関係が推移的であることも保証されなければいけません。(x.compareTo(y)>0 && y.compareTo(z)>0)はx.compareTo(z)>0を意味します。
が保証されない状態になっていたため、これを保証するように修正したところ、正常な動作を得られました。