2015-05-13 2 views
7

Я пытаюсь создать Map с отсортированными клавишами, отсортированными по алфавиту в первую очередь и численным последним. Для этого я использую TreeMap с обычаем Comparator:Java TreeMap пользовательский компаратор странное поведение

public static Comparator<String> ALPHA_THEN_NUMERIC_COMPARATOR = 
    new Comparator<String>() { 

     @Override 
     public int compare(String first, String second) { 
      if (firstLetterIsDigit(first)) { 
       return 1; 
      } else if (firstLetterIsDigit(second)) { 
       return -1; 
      } 
      return first.compareTo(second); 
     } 
    }; 

private static boolean firstLetterIsDigit(String string) { 
    return (string == null) ? false : Character.isDigit(string.charAt(0)); 
} 

Я написал следующее модульное тестирование, чтобы проиллюстрировать то, что идет не так:

@Test 
public void testNumbericallyKeyedEntriesCanBeStored() { 
    Map<String, String> map = new HashMap<>(); 
    map.put("a", "some"); 
    map.put("0", "thing"); 
    TreeMap<String, String> treeMap = new TreeMap<>(ALPHA_THEN_NUMERIC_COMPARATOR); 
    treeMap.putAll(map); 

    assertEquals("some", treeMap.get("a")); 
    assertEquals("thing", treeMap.get("0")); 
} 

С результатом:

java.lang.AssertionError: 
Expected :thing 
Actual :null 

ответ

6

Проверьте код компаратора. Сопоставляет ли «0» и «0» возврат 0, как и следовало? Нет, это не так, поскольку вы не проверяете равенство, если строка начинается с цифры. Вы также не возвращаете правильный порядок, если две строки начинаются с цифр.

+0

Действительно! Но почему была запись null для цифровых клавиш? – Theodor

+0

Поскольку TreeMap возвращает значение только в том случае, если ключ совпадает с ключом запроса через предоставленный компаратор. Ваш компаратор никогда не возвращал 0 при сравнении клавиш «0», поэтому treeMap не смог найти то, что было вставлено. См. Https://docs.oracle.com/javase/7/docs/api/java/util/TreeMap.html#get(java.lang.Object) –

1

Есть некоторые требования для правильной реализации Comparator. Цитирование из документации:

Упорядочение налагается компаратор с на множестве элементов S, как говорят, в соответствии с равным тогда и только тогда, когда c.compare(e1, e2)==0 имеет то же логическое значение, как e1.equals(e2) для каждого e1 и e2 в S.

Это не так для сравнения: comparator.compare("0","0") вернется 1 в вашем случае.

И далее:

следует соблюдать осторожность при использовании компаратора, способный устанавливающего порядок несовместимым с равными заказать отсортированный набор (или отсортированную карту). Предположим, что отсортированный набор (или отсортированная карта) с явным компаратором c используется с элементами (или ключами), взятыми из набора S. Если порядок, наложенный c на S, несовместим с равными, отсортированный набор (или отсортированная карта) будет вести себя «странно». В частности, сортированный набор (или отсортированная карта) нарушит общий контракт для множества (или карты), который определяется в терминах равных.

(акцент на меня - вы можете заменить «странно» с «странно», для вашего случая ;-))

Есть несколько степеней свободы относительно деталей того, как такой компаратор может быть реализован , Например. что должно произойти для таких ключей, как "123isNotNumeric"? Должны ли «цифры» всегда быть одиночными цифрами? Должны ли они всегда быть целыми?

Однако, одна из возможных реализаций может выглядеть следующим образом:

public class SpacialTreeSetComparator 
{ 
    public static void main(String[] args) 
    { 
     TreeMap<String, String> map = new TreeMap<String, String>(
      ALPHA_THEN_NUMERIC_COMPARATOR); 
     map.put("b", "x"); 
     map.put("a", "x"); 
     map.put("1", "x"); 
     map.put("0", "x"); 
     System.out.println(map.keySet()); 
    } 
    public static Comparator<String> ALPHA_THEN_NUMERIC_COMPARATOR = 
     new Comparator<String>() { 

      @Override 
      public int compare(String first, String second) { 

       Double firstNumber = asNumber(first); 
       Double secondNumber = asNumber(second); 
       if (firstNumber != null && secondNumber != null) 
       { 
        return firstNumber.compareTo(secondNumber); 
       } 
       if (firstNumber != null) 
       { 
        return 1; 
       } 
       if (secondNumber != null) 
       { 
        return -1; 
       } 
       return first.compareTo(second); 
      } 
      private Double asNumber(String string) 
      { 
       try 
       { 
        return Double.parseDouble(string); 
       } 
       catch (NumberFormatException e) 
       { 
        return null; 
       } 
      } 
     }; 
} 

Печать keySet() карты печатает ключи в нужном порядке:

[a, b, 0, 1] 
0

код Уплотнитель не является правильным. В случае treeMap.get ("0") равенство не выполняется.

Следующий код в уплотнителе неверен и вызывается проблемой. Компактор также вызывается, когда вы извлекаете некоторый элемент из MAP (чтобы найти соответствующий ключ).В случае «0» ваш буквенно-цифровой код возвращает true и следующий, если условие возвращает 1, поэтому никогда не было найдено равенства «0» true для «0», поэтому возвращать NULL.

if (firstLetterIsDigit(first)) { 
       return 1; 
      } else if (firstLetterIsDigit(second)) { 
       return -1; 
      } 
Смежные вопросы