我在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");
我认为我所做的是我创建了一个按键排序的地图,不区分大小写。两个不同的元素具有不相等的键(abc
和ABC
),其比较将返回0
。我期待这两个元素的随机排序。然而,命令:
System.out.println("treeMap: " + treeMap);
导致:
treeMap: {abc=Element2}
键abc
已重新分配值Element2
!
任何人都可以解释这是如何发生的,以及它是TreeMap
的有效记录行为吗?
答案 0 :(得分:35)
这是因为TreeMap
认为a.compareTo(b) == 0
时元素相等。它记录在the JavaDoc for TreeMap(强调我的)中:
请注意,树形图维护的顺序,如任何有序地图,以及是否提供显式比较器,必须与
equals
一致,如果此有序地图是正确实现Map接口。 (请参阅Comparable
或Comparator
以获得与equals
一致的精确定义。)这是因为Map接口是根据equals
操作定义的,但是已排序map使用其compareTo
(或compare
)方法执行所有关键比较,因此从排序后的地图的角度来看,此方法认为相同的两个键 <强>等于即可。即使排序与equals
不一致,也可以很好地定义有序映射的行为。它只是没有遵守Map接口的一般合同。
你的比较器与equals不一致。
如果你想保持不等于但等于忽略大小的元素,那么对你的比较器进行第二级检查,使用区分大小写的顺序:
public int compare(String o1, String o2) {
int cmp = o1.toLowerCase().compareTo(o2.toLowerCase());
if (cmp != 0) return cmp;
return o1.compareTo(o2);
}
答案 1 :(得分:12)
您传递给Comparator
的{{1}}不仅会确定TreeMap
内的密钥排序,还会确定两个密钥是否相同(Map
时它们被视为相同{1}}返回compare()
)。
因此,在0
,&#34; abc&#34;和&#34; ABC&#34;被认为是相同的键。 TreeMap
不允许使用相同的密钥,因此第二个值Map
会覆盖第一个值Element2
。
答案 2 :(得分:4)
您需要确保该映射元素的相等性与比较器一致。引用课堂评论:
请注意,树图保存的顺序与任何有序地图一样, 以及是否提供明确的比较器必须是 如果这个有序映射是正确的,则与equals一致 实现界面。
答案 3 :(得分:1)
接受的答案在技术上是正确的,但是错过了解决问题的惯用方法。
您应该使用提供的静态String.CASE_INSENSITIVE_ORDER
比较器,或者至少使用自己内部的String.compareToIgnoreCase()
来考虑.equal()
。
对于区域设置敏感的比较,您应该使用java.text.Collator