После первого запуска сервера и инициализации карты всё работало замечательно.
Потом ScalableHashMap был сериализован и сохранен на диск.
Дальше сервер был перезагружен, и ScalableHashMap десериализован с диска.
Делаю ScalableHashMap.get(key) и... получаю null!
Вывожу ScalableHashMap на экран с помощью toString() - там всё есть! Но почему-то он не хочет доставать записанные данные.
Оказалось загвоздка вот в чем:
Enum использует hashCode(), унаследованный от Object(). Следовательно, при перезапуске сервера меняются адреса ссылок, соответственно меняются хеш-коды объектов. И из карты невозможно вытащить объект с новым хеш-кодом, сохраненный по старому хеш-коду!
Казалось бы, проблема решается легко - переопределением hashCode() на что-то вроде
public int hashCode() {
return ordinal();
}
Но не тут-то было! Хеш-код объявлен в enum'е как final!
public final int hashCode() {
return super.hashCode();
}
Получается, надо смириться с тем, что у enum'а при каждом перезапуске приложения может быть новый хеш-код.
Оказывается, для хранения данных с enum-ключом в Java существует специальный класс: java.util.EnumMap, который как раз использует неизменный Enum.ordinal() вместо непостоянного Object.hashCode().
Но, погодите, как же тогда работают HashMap, ConcurrenHashMap и подобные коллекции? Эксперимент показал, что работа с ними не вызывает никаких проблем: enum'ы в качестве ключей нормально сохраняются, и данные нормально вытаскиваются после перезагрузки.
Причина оказалась весьма простой: при сериализации стандартного HashMap его структура и хеш-коды вообще не сериализуются. Сериализуются только ключи и значения, которые при десериализации заново выстраиваются в правильную структуру. Вот так это выглядит в коде jdk:
transient Entry[] table;
transient int size;
...
private void writeObject(java.io.ObjectOutputStream s) throws IOException
{
...
// Write out keys and values (alternating)
if (i != null) {
while (i.hasNext()) {
Map.Entry<K,V> e = i.next();
s.writeObject(e.getKey());
s.writeObject(e.getValue());
}
}
}
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
{
...
// Read the keys and values, and put the mappings in the HashMap
for (int i=0; i<size; i++) {
K key = (K) s.readObject();
V value = (V) s.readObject();
putForCreate(key, value);
}
}
В результате этого небольшого расследования-исследования можно сделать следующие выводы:
- При работе с enum'ами надо всегда помнить, что их хеш-код меняется при каждом перезапуске.
- При написании собственных реализаций коллекций надо также помнить об этом свойстве enum'a.
- По возможности использовать EnumMap, если ключом является enum.

Комментариев нет:
Отправить комментарий