Java HashMap не может найти ключ из события ListDataListener

Может кто-нибудь объяснить, почему HashMap ведет себя так, как в этом примере:
Простой тест, который проверяет хэш-карту на наличие ключа. Один раз в конструкторе и один раз в методе ListDataListener intervallAdded.

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;

import com.jgoodies.common.collect.ArrayListModel;

public class Test1 {

  private final Listener listener = new Listener();
  private final Map<List<?>, Object> parentByCollection = new HashMap<List<?>, Object>();

  public Test1(){
    ArrayListModel<Object> list = new ArrayListModel<Object>();

    list.addListDataListener(listener);

    parentByCollection.put(list, new Integer(10));

    // Test containsKey locally
    System.out.println("Item exists (locally):" + parentByCollection.containsKey(list));

    // Test containsKey via ListDataListener
    list.add(new Integer(20));
  }

  /**
   * @param args
   */
  public static void main(String[] args) {
    new Test1();
  }

  public class Listener implements ListDataListener{

    @Override
    public void intervalAdded(ListDataEvent e) {
      List<?> itemSource = (List<?>)e.getSource();

      System.out.println("Item exists (listener):" + parentByCollection.containsKey(itemSource));      
    }

    @Override
    public void intervalRemoved(ListDataEvent e) {
    }

    @Override
    public void contentsChanged(ListDataEvent e) {
    }
  }
}

Почему хэш-карта возвращает false из события, но true из конструктора при использовании containsKey? Есть ли какая-то «магия» java-дженериков, о которой я здесь не знаю?

Редактировать:

Только что обнаружил, что метод hashCode ArrayList (который расширяет ArrayListModel) собирает свой хэш-код из всех его элементов. Это означает, что хэш-код меняется с элементами в списке. Так что хранить ArrayList в HashMap не очень хорошая идея.

Как я могу это решить? Вместо этого хранить коллекции в объекте держателя/контейнера?


person Marcus    schedule 13.03.2011    source источник
comment
Предоставьте реализацию метода hashCode() для com.jgoodies.common.collect.ArrayListModel.   -  person Stan Kurilin    schedule 13.03.2011
comment
Вы можете найти источники здесь jgoodies.com/downloads/libraries.html ArrayListModel расширяет ArrayList и не переопределяет hashCode, поэтому я предполагаю, что используется hashCode ArrayList. Но я не понимаю, насколько это актуально, первый вызов containsKey возвращает true.   -  person Marcus    schedule 13.03.2011


Ответы (2)


Я знаю, что теперь вы понимаете проблему, но вот объяснение для других людей:

HashMap использует хэш-код ключа, который в данном случае является хэш-кодом списка.

Глядя на javadoc метода хэш-кода списка, можно понять, что хэш-код списка зависит от содержащихся элементов, чтобы соблюдать контракт между хэш-кодом и равенством.

Из-за контракта любая последующая модификация списка, приводящая к изменению равенства, также приведет к изменению хэш-кода, и, таким образом, Hashmap не сможет получить первоначальный список.

Решение в этом случае — использовать ссылку, она не изменится при добавлении или удалении элементов из списка. А вот клон списка (равный ему) работать не будет!

person Christophe Roussy    schedule 13.03.2011

Проблема была совершенно очевидна, когда я просматривал исходники. Решение для хранения коллекций на карте состоит в том, чтобы НЕ использовать HashMap, а вместо этого использовать карту, основанную на таких ссылках, как apache commons ReferenceIdentityMap или java.util.IdentityHashMap.

person Marcus    schedule 13.03.2011
comment
или повторно реализовать метод hashCode. - person Stan Kurilin; 13.03.2011
comment
Конечно, это тоже решение. Но гораздо более чистым решением является использование карты, которая работает с ссылочным равенством для ключей. - person Marcus; 13.03.2011
comment
Гораздо более чистое решение - не использовать списки в качестве ключей для карты) - person Stan Kurilin; 13.03.2011
comment
@Stas: Если не использовать список в качестве ключа, как я могу найти соответствующий объект внутри события intervallAdded? Все, что у меня есть, это e.getSource() - person Marcus; 13.03.2011