不区分大小写的比较器会破坏我的TreeMap

jav*_*ian 20 java comparator

一个Comparator在我使用我TreeMap打破了我的意图的行为TreeMap.看下面的代码:

TreeMap<String, String> treeMap = new TreeMap<>(new Comparator<String>() {
    public int compare(String o1, String o2) {
        return o1.toLowerCase().compareTo(o2.toLowerCase());
    }
});
treeMap.put("abc", "Element1");
treeMap.put("ABC", "Element2");
Run Code Online (Sandbox Code Playgroud)

我认为我所做的是我创建了一个按键排序的地图,不区分大小写.两个不同的元素具有不相等的键(abcABC),其比较将返回0.我期待这两个元素的随机排序.然而,命令:

System.out.println("treeMap: " + treeMap);
Run Code Online (Sandbox Code Playgroud)

导致:

treeMap: {abc=Element2}
Run Code Online (Sandbox Code Playgroud)

钥匙abc已重新分配价值Element2!

任何人都可以解释这是怎么发生的,如果它是一个有效的,记录在案的行为TreeMap

And*_*ner 36

它发生是因为TreeMap认为元素相等a.compareTo(b) == 0.它在JavaDoc for TreeMap(强调我的)中有记录:

请注意,树映射维护的顺序(如任何有序映射)以及是否提供显式比较器必须与equals此有序映射是否正确实现Map接口一致.(参见ComparableComparator查看与...一致的精确定义equals.)这是因为Map接口是根据equals操作定义的,但是有序映射使用其compareTo(或compare)方法执行所有键比较,因此两个被认为相等的键从排序映射的角度来看,这种方法是相同的.即使排序不一致,排序映射的行为也是明确定义的equals; 它只是不遵守Map接口的一般合同.

你的比较器与equals不一致.

如果你想保持不等于但等于忽略大小的元素,那么对你的比较器进行第二级检查,使用区分大小写的顺序:

    public int compare(String o1, String o2) {
        int cmp = o1.compareToIgnoreCase(o2);
        if (cmp != 0) return cmp;

        return o1.compareTo(o2);
    }
Run Code Online (Sandbox Code Playgroud)

  • @JAB:我不确定我是否同意.只要您遵守其界面的保证,`TreeMap`类就会遵守`Map`界面的保证!它的文档明确指出比较器必须与`equals`一致.同样地,如果`equals`和`hashCode`不一致,任何`HashMap`都会出错 - 你是否认为这"不是很好"?我的观点是:合同是双向的. (7认同)

Era*_*ran 12

Comparator你传递给TreeMap决定不内键的只是排序Map,这也决定两个键是否被认为是相同的(它们被认为是相同的时候compare()返回0).

因此,在你的TreeMap"abc"和"ABC"被认为是相同的键.Maps不允许使用相同的键,因此第二个值会Element2覆盖第一个值Element1.