Ключ в TreeMap, возвращающий null

Итак, у меня есть очень странный баг. Я наткнулся на него, когда изначально использовал keySet() для перебора первых 10 ключей большого TreeMap. Один из ключей возвращал значение null, что, насколько я понимаю, невозможно. Поэтому я написал тестовый код ниже:

int i = 0;
        for (Map.Entry<String, Integer> es : sortedMap.entrySet()){
            if (i >= 10) {
                break;
            }

            if (sortedMap.containsKey(es.getKey())){
                System.out.println(es.getKey() + ":" + sortedMap.get(es.getKey()));
            } else {
                System.out.println("Key " + es.getKey() + " does not exist, yet...");
                System.out.println("This does work: " + es.getKey() + ":" + es.getValue());
                System.out.println("This does NOT work: " + es.getKey() + ":" + sortedMap.get(es.getKey()));
            }
            i++;
        }

И получить следующие результаты:

SOAP:967
'excerpt'::679
'type'::679
Key 'author_url': does not exist, yet...
This does work: 'author_url'::679
This does NOT work: 'author_url'::null
'date'::679
Android:437
TLS:295
message:283
server:230
monthly:215
<<<<<<<<<<<<<<<<<<<<DUMPING MAP!
{SOAP=967, 'excerpt':=679, 'type':=679, 'author_url':=679, 'date':=679, Android=437, TLS=295, message=283, server=230, monthly=215...

Я обрезал карту после первой десятки, так как там много чего еще, но все это ключ со значением.

Итак, мой вопрос заключается в следующем: почему я получаю нулевое значение при использовании ключа для прямого получения (ключа) из TreeMap, но EntrySet возвращает правильный ключ и значение?

Вот мой компаратор, так как я заказываю Integer:

class ValueComparator implements Comparator<Object> {

  Map<String, Integer> base;
  public ValueComparator(Map<String, Integer> base) {
      this.base = base;
  }

  public int compare(Object a, Object b) {

    if ((Integer) base.get(a) < (Integer) base.get(b)) {
      return 1;
    } else if ((Integer) base.get(a) == (Integer) base.get(b)) {
      return 0;
    } else {
      return -1;
    }
  }
}

И TreeMap строится следующим образом:

ValueComparator bvc =  new ValueComparator(allMatches);
TreeMap<String, Integer> sortedMap = new TreeMap<String, Integer>(bvc);
//Sort the HashMap
sortedMap.putAll(allMatches);

Где allMatches — это HashMap<String, Integer>


person Dennis Sullivan    schedule 24.02.2012    source источник
comment
Вы используете необычный компаратор для TreeMap? Если да, то можем ли мы увидеть его код? Судя по вашему дампу, вы не используете порядок String по умолчанию...   -  person Louis Wasserman    schedule 24.02.2012
comment
@LouisWasserman Добавлен мой компаратор.   -  person Dennis Sullivan    schedule 25.02.2012
comment
Почему у вас есть конструктор и член в этом классе? Компаратор обычно вызывается для каждого элемента, поэтому вам не следует ссылаться на результирующую карту. Весь ваш код был бы полезен.   -  person tom    schedule 25.02.2012
comment
Этот вопрос потенциально имеет отношение к вашей проблеме...   -  person smessing    schedule 25.02.2012
comment
В чем проблема с Один из ключей возвращал null? TreeSet допускает нули   -  person Oleg Mikheev    schedule 25.02.2012
comment
TreeMap.get(entry.getKey()) должно возвращать то же самое, что и entry.getValue(), при условии, что entry получено из ввода map. Это проблема.   -  person Louis Wasserman    schedule 25.02.2012


Ответы (4)


Судя по порядку итерации, который показывает ваш TreeMap, это определенно тот случай, когда вы использовали пользовательский Comparator. [Иначе итерация была бы в лексикографическом порядке]

Обратите внимание, что согласно javadocs:

Разработчик должен гарантировать, что sgn(compare(x, y)) == -sgn(compare(y, x)) для всех x и y. (Это означает, что сравнение(x, y) должно выдавать исключение тогда и только тогда, когда сравнение(y, x) выдает исключение.)

Разработчик также должен убедиться, что отношение является транзитивным: ((compare(x, y)>0) && (compare(y, z)>0)) подразумевает сравнение(x, z)>0.

Наконец, разработчик должен убедиться, что сравнение (x, y) == 0 подразумевает, что sgn (compare (x, z)) == sgn (compare (y, z)) для всех z.

Если ваш Comparator не применяет эти правила - поведение не определено, так как могут показывать странные результаты - как видите.

EDIT: [как ответ на отредактированный вопрос]
Ваш компаратор использует идентификатор [operator==] для проверки двух целых чисел.
Обратите внимание, что Integer является объектом, поэтому operator== вернет true только в том случае, если это тот же объект.
Вы должны использовать equals() чтобы проверить, идентичны ли два целых числа, или даже лучше, используйте Integer.compareTo()

person amit    schedule 24.02.2012
comment
Использование compareTo приводит к сворачиванию TreeMap для любого равного значения. {monthly=215, server=230, message=283, TLS=295, Android=475, excerpt=679, SOAP=967} - person Dennis Sullivan; 25.02.2012
comment
Действительно, и это намек на то, что ваш код еще более сломан. Вы вообще не можете использовать TreeMap, не то чтобы вы пытаетесь его использовать. - person Louis Wasserman; 25.02.2012
comment
@DennisSullivan: В дополнение к тому, что сказал Луи: прочитайте прилагаемые документы по Java. Вы должны убедиться, что ваш компаратор удовлетворяет написанным условиям, иначе поведение не определено. - person amit; 25.02.2012
comment
Up проголосовал за то, чтобы помочь моему мышлению прийти к правильному ответу. - person Dennis Sullivan; 27.02.2012

Ваша самая большая проблема заключается в том, что использование вами == вместо .equals в вашем компараторе значений нарушает работу, потому что разные ключи сопоставляются с разными объектами Integer с одним и тем же intValue(), что непредсказуемо отбрасывает еще больше вещей.

Но если вы исправите это, то ваш TreeMap не позволит вам вставлять несколько ключей с одинаковым значением, что почти наверняка также вызывает тонкие поломки.

Лучшим решением было бы что-то вроде этого, но в основном вы должны заполнить карту без сортировки по значениям, отсортировать entrySet, а затем скопировать записи (по порядку) на карту, такую ​​​​как LinkedHashMap, которая не нуждается в компараторе, а просто сохраняет записи в порядок вставки.

Вы можете изменить свой компаратор, чтобы, если значения совпадают, он также сравнивал ключи. Это, по крайней мере, позволит вам вставить несколько ключей с одним и тем же значением... но это все еще действительно хакерское решение, которое намного более рискованно, чем решение на основе LinkedHashMap, как описано выше.

person Louis Wasserman    schedule 24.02.2012
comment
Up проголосовал за то, чтобы помочь моему мышлению прийти к правильному ответу. - person Dennis Sullivan; 27.02.2012

Задача решена:

class ValueComparator implements Comparator<Object> {

Map<String, Integer> base;

public ValueComparator(Map<String, Integer> base) {
    this.base = base;
}

public int compare(Object a, Object b) {

    if (((Integer) base.get(a)).intValue() < ((Integer) base.get(b)).intValue()) {
        return 1;
    } else if ( ((Integer) base.get(a)).intValue() == ((Integer) base.get(b)).intValue()) {
        return ((String)a).compareTo(((String)b));
    } else {
        return -1;
    }
}
}

Это связано с дополнительным преимуществом возврата ключей с одинаковым значением в алфавитном порядке.

person Dennis Sullivan    schedule 27.02.2012

Вы должны просто иметь:

class ValueComparator implements Comparator<Integer> {


  public int compare(Integer a, Integer b) {
      return a.compareTo(b);
  }
}

Затем вам нужно инициализировать вашу карту дерева с помощью компаратора и добавить все ваши элементы:

древовидная карта

person tom    schedule 24.02.2012
comment
Он использует его для сравнения значений, что не сработает с TreeMap, не совсем =( - person Louis Wasserman; 25.02.2012
comment
Ты прав. Я думал, что он сортирует по ключам (как обычно), но он сортирует по значениям. Я неправильно понял вопрос. Все, что я могу сказать, это то, что сортировка по значениям обычно не выполняется на карте. Для этого лучше подходят списки. - person tom; 25.02.2012