2017-10-24 58 views
19

A Comparator użyłem w moim TreeMap zepsułem zachowanie przeznaczone dla tego TreeMap. Spójrz na poniższy kod:Case-niewrażliwy Komparator łamie moje 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"); 

Co myślę, że zrobili to, że stworzyli mapę, która jest posortowana według swoich kluczy, bez uwzględniania wielkości liter. Dwa różne elementy mają klucze nie równe (abc i ABC), których porównanie powróci 0. Spodziewałem się tylko losowego uporządkowania dwóch elementów. Jednak komenda:

System.out.println("treeMap: " + treeMap); 

następująco:

treeMap: {abc=Element2} 

Kluczem abc została ponownie przypisana wartość Element2!

Czy ktoś może wyjaśnić, jak to się stało i czy jest to prawidłowe, udokumentowane zachowanie TreeMap?

+0

'Comparator' jest * całkowity porządek *. Co to znaczy, że "abc" i "ABC" znajdują się na twojej mapie? Co jest pierwsze? –

+2

masz swoje zamierzone (jeśli nieoczekiwane) zachowanie; Twój komparator nie rozróżnia wielkości liter, twoja mapa używa tego komparatora - więc jest to również, w dużym stopniu, bez rozróżniania wielkości liter (klucze w.r.t.). * Po prostu wymyśliłeś, jak zrobić mapę, która nie jest wrażliwa na wielkość liter * - dla ciebie, ale nie widzę tutaj nic "szokującego" lub "nielegalnego" ... robi dokładnie to, co ty * powiedziałeś *. – vaxquis

+0

powiązane: [Java TreeMap niestandardowe dziwne zachowanie komparatora] (https://stackoverflow.com/questions/30219835/java-treemap-custom-comparator-weird-behavour) –

Odpowiedz

35

Dzieje się tak, ponieważ TreeMap uważa elementy za równe, jeśli a.compareTo(b) == 0. Jest udokumentowane w the JavaDoc for TreeMap (kopalnia nacisk):

Zauważ, że kolejność utrzymywany przez drzewa mapie, jak każdy posortowanej mapie i czy nie wyraźne komparator jest, muszą być zgodne z equals jeśli to sortowane mapowanie to poprawna implementacja interfejsu Map. (Patrz Comparable lub Comparator dla precyzyjnej definicji zgodne z equals). Jest tak dlatego, że interfejs Map jest definiowana w kategoriach operacji equals, ale klasyfikowane map wykonuje wszystkie kluczowe porównań przy użyciu metody jego compareTo (lub compare), więc dwa klucze, które są uznawane za równe tą metodą, to, z punktu widzenia posortowanej mapy, równe. Zachowanie posortowanej mapy jest dobrze zdefiniowane, nawet jeśli jej kolejność jest niezgodna z equals; po prostu nie przestrzega ogólnej umowy interfejsu Map.

Twój komparator nie jest zgodny z równymi.

Jeśli chcesz zachować nie równy, ale równy 'ignorując literami pierwiastków, umieścić drugi poziom kontroli w swojej komparatora, aby korzystać wielkości liter Kolejność:

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

     return o1.compareTo(o2); 
    } 
+0

Dzięki, chłopaki, za szybkie odpowiedzi! Teraz widzę to wyraźnie w dokumentach, chociaż nadal uważam to zachowanie za sprzeczne z intuicją po stronie Java. W końcu TreeMap służy do sortowania, a sortowanie jest prawdopodobnie najczęściej wykonywane na tekstach w sposób niewrażliwy na wielkość liter. – javaxian

+0

@javaxian TreeMap służy do utrzymywania przedmiotów na mapie wspieranej przez drzewo, sortowanie jest bardziej efektem ubocznym używania drzewa. Ale tak, jest to sprzeczne z intuicją, ponieważ narusza gwarancje interfejsu Map. Niezbyt miły z programistów Java. – JAB

+7

@JAB: Nie jestem pewien, czy się zgadzam. Klasa 'TreeMap' jest zgodna z gwarancjami interfejsu' Map', pod warunkiem, że zastosujesz się do gwarancji jego interfejsu! Jego dokumentacja wyraźnie stwierdza, że ​​komparator musi być zgodny z 'equals'. Podobnie każde 'HashMap' będzie źle wyglądać, jeśli' equals' i 'hashCode' nie są spójne - czy ty też uważasz, że jest to" niezbyt miłe "? Chodzi mi o to, że kontrakty idą w obie strony. – wchargin

12

Comparator przechodzą do TreeMap decyduje nie tylko kolejność kluczy wewnątrz Map, ale także określa, czy dwa klawisze są uważane za identyczne (są one uważane za identyczne, jeśli compare() powraca 0).

Dlatego w twoim TreeMap "abc" i "ABC" są uważane za identyczne klucze. Map s nie pozwalają na identyczne klucze, więc druga wartość Element2 zastępuje pierwszą wartość Element1.

4

Musisz się upewnić, że równość elementów mapy jest zgodna z komparatorem.Cytując z komentarzem klasy:

Zauważ, że kolejność utrzymywany przez drzewa mapie, jak każdy posortowanej mapie, i czy nie wyraźne komparator jest, musi być zgodny z równymi, czy to sortowych mapa jest do poprawnie zaimplementuj interfejs.

1

Przyjęta odpowiedź jest technicznie poprawna, ale pomija idiomatyczne rozwiązanie problemu.

Powinieneś używać statycznego kompilatora String.CASE_INSENSITIVE_ORDER, lub przynajmniej używać w swoim własnym komputerze do rozważenia, co to jest .equal().

Dla wrażliwych porównań locale, należy użyć coś z java.text.Collator

+5

To prawda, że ​​programiści powinni używać istniejących komparatorów lub używać dedykowanych metod porównawczych zamiast kosztownego wzoru "przekonwertuj na małe litery". Ale to nie rozwiązuje problemu. Nadal potrzebujesz komparatora dwuetapowego. Rozwiązaniem Java 8 będzie 'String.CASE_INSENSITIVE_ORDER.thenComparing (Comparator.naturalOrder())'. – Holger

+0

@AndyTurner masz całkowitą rację co do używania wbudowanych komparatorów (lub Collator w tym przypadku) zamiast pisania własnych. Jednak w moim kodzie produkcyjnym porównywałem obiekty, używając ich zmiennych całkowitych presentationOrder, które w niektórych przypadkach miały tę samą wartość. Nie chciałem, aby mój przykład był zbyt skomplikowany, więc wybrałem opcję nie uwzględniającą wielkości liter. – javaxian