読者です 読者をやめる 読者になる 読者になる

しおしお

IntelliJのあれやこれや

ソートマップ(TreeMap)のキーの等価性

ソートマップ(java.util.TreeMap)を使用した場合、キーの等価性の判断にはequalsではなくComparable#compareToの結果が使われます。 *1
これは、
Map (Java Platform SE 6)
の規約に違反していますが、TreeMapのドキュメントにはMapの規約に違反していることが明記されています。*2
このため、独自オブジェクトをTreeMapのキーに使用する場合は、equalsを実装しても実行されることがないのでカバレッジが低くなったりするので注意が必要です。基本的に独自オブジェクトをキーに突っ込むなんてことしないはずですが・・・

ちょっとしたお試しコードです。equalsを実装していないオブジェクトをキーにぶち込んだ場合のTreeMapとHashMapのcontainsKeyを呼び出した結果がわかるようになっています。

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class Main {

    public static void main(String[] args) {

        MapKey key1 = new MapKey("1", "2");
        MapKey key2 = new MapKey("1", "2");

        Map<MapKey, String> treeMap = new TreeMap<MapKey, String>();
        treeMap.put(key1, "1");
        // TreeMapの場合は、equalsを実装していなくてもcompareToの結果で等価性が判断される
        System.out.println("treeMap.containsKey(key2) = " + treeMap.containsKey(key2));


        HashMap<MapKey, String> hashMap = new HashMap<MapKey, String>();
        hashMap.put(key1, "1");
        // ソートマップではないので、java.util.Mapの規約どおりにequalsの結果で等価性が判断される。
        // MapKeyは、equalsをoverrideしていないので、key1とkey2では等価にはならない
        System.out.println("hashMap.containsKey(key2) = " + hashMap.containsKey(key2));

    }

    private static class MapKey implements Comparable<MapKey> {

        private final String key1;

        private final String key2;

        private MapKey(String key1, String key2) {
            this.key1 = key1;
            this.key2 = key2;
        }

        @Override
        public int compareTo(MapKey o) {
            int result = key1.compareTo(o.key1);
            if (result == 0) {
                result = key2.compareTo(o.key2);
            }
            return result;
        }
    }
}

実行結果は、TreeMapの場合はキーが存在していると判断されますが、HashMapの場合は存在していないとなります。

treeMap.containsKey(key2) = true
hashMap.containsKey(key2) = false

TreeMapなんてめったに使わないし、たいていStringをキーにするのでこんな仕様があることは全く知りませんでした。

おわり。

*1:compareToの結果が「0」の場合等価となる。

*2:TreeMapのドキュメントには、「ソートマップの動作は、その順序付けが equals と一貫性がない場合でも明確に定義されていますが、Map インタフェースの一般規約には準拠していません。」とあります。