Java 不区分大小写的比较器破坏了我的树映射

Java 不区分大小写的比较器破坏了我的树映射,java,comparator,Java,Comparator,我在TreeMap中使用的Comparator破坏了我打算用于该TreeMap的行为。请看以下代码: TreeMap<String, String> treeMap = new TreeMap<>(new Comparator<String>() { public int compare(String o1, String o2) { return o1.toLowerCase().compareTo(o2.toLowerCase())

我在
TreeMap
中使用的
Comparator
破坏了我打算用于该
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");
导致:

treeMap: {abc=Element2}
abc
已重新分配值
Element2


有人能解释这是怎么发生的吗?如果这是
TreeMap
的有效记录行为吗?

传递给
TreeMap
比较器
不仅确定
映射中键的顺序,还确定两个键是否相同(当
compare()
返回
0
时,它们被认为是相同的)


因此,在
TreeMap
中,“abc”和“abc”被视为相同的键。
Map
s不允许使用相同的键,因此第二个值
Element2
覆盖第一个值
Element1

TreeMap
认为元素相等,如果
a.compareTo(b)==0
。它记录在(我的)中:

请注意,树映射维护的顺序与任何排序映射一样,无论是否提供显式比较器,必须与
等于
一致,如果此排序映射要正确实现映射接口。(请参见
Comparable
Comparator
以获得与
相等的一致性的精确定义)之所以如此,是因为映射接口是根据
equals
操作定义的,但排序映射使用其
compareTo
执行所有关键比较(或
compare
)方法,因此该方法认为相等的两个键是,从排序映射的角度来看,相等。即使排序与
相等
不一致,排序映射的行为也是定义良好的;它只是没有遵守映射接口的一般约定

你的比较器与equals不一致

如果要忽略大小写元素而保持不相等,请在比较器中进行第二级检查,以使用区分大小写的排序:

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

        return o1.compareTo(o2);
    }

您需要确保映射元素的相等性与比较器一致。引用类注释:

请注意,由树映射维护的排序与任何排序映射一样, 无论是否提供了明确的比较器,都必须 如果要正确设置此排序映射,则与等于一致 实现接口


公认的答案在技术上是正确的,但没有找到问题的惯用解决方案

你应该使用所提供的静态比较器,或者至少使用你自己的内部来考虑什么是<代码>。


对于区域敏感的比较,你应该从

中使用一些东西,谢谢,伙计们,快速回答!现在我看到它在文档中清楚地表达了,尽管我仍然认为这个行为在java方面是违反直觉的。毕竟,TreeMap是用来排序的,排序通常是以不敏感的方式在文本上执行的。eMap用于维护树支持的地图中的项目,排序更多的是树使用的副作用。但是,是的,它违反了地图界面的保证,这是违反直觉的。Java开发人员不是很好。@JAB:我不确定我是否同意。
TreeMap
类遵守
map
interf的保证ace,前提是您遵守其接口的保证!其文档清楚地说明比较器必须与
equals
一致。同样,如果
equals
hashCode
不一致,任何
HashMap
都会出现错误行为。您是否也认为这“不太好”?我的观点是:契约是双向的。@wchargin,
HashMap
的行为不是很好,仅仅因为它让用户有责任去做正确的事情。我们可以到处检查一致性(类似于“comparator违反了一般契约”);但是,如果您已经阅读了文档并相应地实现了comparator,那么这些检查的额外成本是不必要的。(我同意您的评论,顺便说一句)我必须承认,将属性注释为
可识别的
,然后由这些属性自动计算出equals、hashcode和compareTo是很方便的(显然,compare to需要知道比较它们的顺序)-是的,你需要能够以老式的方式进行比较,但让我们面对它,在99%的情况下,hashcode和equals只使用一个或两个字段。
Comparator
是一个总的顺序。它甚至对“abc”和“abc”意味着什么在你的地图中?哪一个先出现?你得到了你想要的(如果意外的话)行为;你的比较器是不区分大小写的,你的地图使用这个比较器-因此,它也是不区分大小写的(w.r.t.键)。你刚刚想出了如何使地图不区分大小写-这对你来说很好,但我看不到任何“令人震惊”或“非法”的东西在这里,它完全按照你告诉它的做。相关:开发人员应该使用现有的比较器,或者使用专用的比较方法,而不是昂贵的“转换为小写”是正确的反模式。但这并不能解决问题。您仍然需要一个两步比较器。Java 8解决方案将是
字符串。不区分大小写\u顺序。然后比较(comparator.naturalOrder())
@AndyTurner您使用内置比较器(或本例中的Collator)是绝对正确的而不是编写我自己的。然而,在我的生产代码中,我使用对象的presentationOrder整数字段来比较对象,这些字段在某些情况下具有相同的值。我不想让我的示例过于复杂,所以我使用了不区分大小写的字符串比较器。
    public int compare(String o1, String o2) {
        int cmp = o1.compareToIgnoreCase(o2);
        if (cmp != 0) return cmp;

        return o1.compareTo(o2);
    }