Компаратор, нечувствительный к регистру, разбивает мою TreeMap
A 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
?
Ответы
Ответ 1
Это происходит потому, что TreeMap
считает элементы равными, если a.compareTo(b) == 0
. Он документально подтвержден в JavaDoc для TreeMap (выделено мной):
Обратите внимание, что упорядочение, поддерживаемое древовидной картой, как и любая сортированная карта, а также наличие явного компаратора, должен соответствовать equals
, если эта отсортированная карта должна правильно реализовать интерфейс карты. (См. Comparable
или Comparator
для точного определения, согласующегося с equals
.) Это связано с тем, что интерфейс Map определен в терминах операции equals
, но отсортированная карта выполняет все сопоставления ключей, используя ее compareTo
(или compare
), поэтому два ключа, которые по этому методу считаются равными, с точки зрения отсортированной карты равно. Поведение сортированного отображения хорошо определено, даже если его порядок не согласуется с equals
; он просто не соблюдает общий контракт интерфейса карты.
Ваш компаратор не согласуется с равными.
Если вы хотите сохранить элементы с не равным, но равным-игнорируемому случаю, добавьте второй уровень проверки в ваш компаратор, чтобы использовать регистр, чувствительный к регистру:
public int compare(String o1, String o2) {
int cmp = o1.toLowerCase().compareTo(o2.toLowerCase());
if (cmp != 0) return cmp;
return o1.compareTo(o2);
}
Ответ 2
Comparator
, который вы передаете в TreeMap
, определяет не только порядок ключей внутри Map
, но также определяет, считаются ли две клавиши одинаковыми (они считаются идентичными, когда compare()
возвращает 0
).
Поэтому в ваших TreeMap
, "abc" и "ABC" считаются идентичными клавишами. Map
не разрешать идентичные ключи, поэтому второе значение Element2
перезаписывает первое значение Element1
.
Ответ 3
Вам нужно убедиться, что равенство этих элементов карты согласуется с компаратором. Цитата из комментария к классу:
Обратите внимание, что упорядочение, поддерживаемое древовидной картой, как любая сортированная карта, и должен ли явный компаратор быть установленным, должно быть согласуется с равенствами, если эта отсортированная карта правильно реализовать интерфейс.
Ответ 4
Принятый ответ технически корректен, но не учитывает идиоматическое решение проблемы.
Вы должны использовать статический String.CASE_INSENSITIVE_ORDER
, предоставленный компаратором или, по крайней мере, используя String.compareToIgnoreCase()
внутри вашего собственного, чтобы рассмотреть, что такое .equal()
.
Для локальных чувствительных сравнений вы должны использовать что-то из java.text.Collator