Настройка механизма сериализации
Поможем в ✍️ написании учебной работы
Поможем с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой

Схема сериализации/десериализации, предлагаемая по умолчанию, вполне работоспособна во многих ситуациях, но, увы, не во всех. Для некоторых классов обычные средства десериализации могут оказаться неадекватными или неэффективными. Наглядным примером способен служить класс HashMap . Следуя обычной схеме сериализации, можно было бы сохранить в потоке все структуры данных хеш-таблицы, включая и значения хеш-кодов её элементов. Такой подход окажется и неверным, и неэффективным. Неправильность подхода обусловлена тем, что значения хеш-кодов восстановленных элементов могут отличаться от исходных, - в частности, если используется реализация hashCode , предусмотренная по умолчанию. Неэффективность обычной схемы сериализации в применении к хеш-таблицам объясняется тем, что многие из элементов хеш-таблиц, как правило, бывают пустыми и выполнять их сериализацию совершенно нецелесообразно. Более уместно сохранять в потоке только те элементы, ссылки на которые существуют, нежели осуществлять сериализацию всех данных таблицы.

Руководствуясь подобными соображениями, создатели Java предусмотрели возможность включать в состав класса специальные private – версии методов writeObject и readObject. Методы активизируются потоками ObjectOutputStream и ObjectInputStream соответственно в тот момент, когда необходимо осуществить сериализацию и десериализацию объекта. Подобные переопределённые варианты методов writeObject и readObject вызываются только в контексте объектов соответствующих классов, и методы несут ответственность исключительно за состояние объекта класса как такового, в том числе и в части, унаследованной от базового класса, не поддерживающего механизм сериализации. Если в составе класса реализованы собственные варианты методов writeObject и readObject, они не должны обращаться к одноимённым методам базового класса.

private void writeObject(ObjectOutputStream out) throws IOException

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException

В объявлении метода writeObject обозначена вероятность выбрасывания им исключения типа IOException , так как оно может быть сгенерировано любым из «низкоуровневых» методов write , которые будут здесь вызываться, и если подобное произойдёт, процесс сериализации должен быть завершён аварийным образом. В предложении throws объявления метода readObject также следует упомянуть исключение IOException – оно может быть выброшено вызываемыми методами read , а в такой ситуации процесс десериализации следует остановить. Помимо того, метод readObject способен генерировать и исключение типа ClassNotFoundException , так как в общем случае при десериализации полей текущего объекта может потребоваться загрузка и других классов.

Существует ограничение, которое следует принять во внимание, приступая к настройке механизма сериализации: нельзя присваивать значение полю final в теле метода readObject, поскольку поля final могут быть проинициализированы только в блоках инициализации или конструкторах. Механизм сериализации, предусмотренный по умолчанию, однако, способен «обходить» это ограничение – он вполне нормально работает и с теми классами, в составе которых имеются поля final.

Контроль версий объектов

Реализация классов со временем изменяется. Если подобное происходит в промежутке времени между сериализацией и десериализацией объекта этого класса, поток ObjectInputStream способен обнаружить факт внесения изменений. При сохранении объекта вместе с ним записывается уникальный идентификатор номера версии – 64-битовое значение типа long. По умолчанию идентификатор создаётся в виде хеш-кода, построенного на основе информации об именах класса, его членов и базовых интерфейсов. Изменение таких данных служит сигналом о возможной несовместимости версий класса.

При вводе данных об объекте из потока ObjectInputStream считывается также и идентификатор номера версии. Затем предпринимается попытка загрузки соответствующего класса. Если требуемый класс не найден, либо идентификатор загруженного класса не совпадает с тем, который считан из потока, метод readObject выбрасывает исключение типа InvalidClassException. Если успешно загружены все нужные классы и выявлено совпадение всех идентификаторов, объект, как принято считать, может быть подвергнут десериализации.

Таким образом, получается, что любое, даже самое незначитнельное, изменение в структуре класса способно привести к несовместимости версий. Но во многих случаях модификация классов носит отнюдь не радикальный характер. Существует возможность сохранить совместимость класса, снабжённого дополнительными несущественными нововведениями, с данными ранее сериализованных объектов. Для этого следует принудительно объявить в составе класса специальное поле, содержащее значение идентификатора номера версии, например:

private static final long serialVersionUID = ...;

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

Теперь, когда поток ObjectInputStream находит описание требуемого класса и сопоставляет его идентификатор с тем, который сохранён вместе с сериализованными данными, проблема несовместимости не возникнет, даже если реализация класса на самом деле подверглась изменению.

Дата: 2019-02-19, просмотров: 204.