Skip to content
Aleksandr Kuchuk edited this page Aug 8, 2016 · 29 revisions

###Примеры кода: Все примеры в модуле code-examples пакета serialization. Примеры

Зачем?

Для сохранения состояния объектов. При этом мы объекты можем сохранять как в binary, так и в файлы. А после уже оттуда забирать их состояние и получать наши объекты.

Binary Serialization

Введение

Проще всего сделать объект сериализуемым - это реализовать java.io.Serializable интерфейс. Многие же и вовсе считают, что так только и можно сделать.

Но нет, есть еще способы.

Мы можем:

  • реализовать java.io.Serializable
  • реализовать Externalizable

Подробнее о каждом - ниже.

Serializable interface

Простейший и самый часто используемый вариант. Стандартная сериализация в java работает через Reflection API, класс раскладывается на поля, метаданные и пишется в output-поток. Важно понимать, что раз мы используем Reflection API - это не слишком оптимально по части производительности.

И еще важное замечание - если мы используем Serializable, то при десериализации мы не вызываем конструктор класса. Это надо помнить.

Теперь рассмотрим такой случай.

У нас есть класс у которого также есть супер-класс родитель. Наш класс - serializable, будет ли родитель serializable? Нет, не будет! Поля родителя в поток не попадут. Тогда как будет работать при дессериализации? Когда мы попытаемся дессериализовать наш класс - Мы вызовем конструктор супер класса(родительского) без параметров! Сам же конструктор дессериализуемого класса не вызовется, ведь мы используем Serializable. При это если у супер класса нет такого конструктора - мы поймаем ошибку.

Еще один интересный момент: Если мы реализуем Serializable интерфейс и в класс добавим методы:

 private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException;
 private void writeObject(java.io.ObjectOutputStream stream) throws IOException;
 private void readObjectNoData() throws ObjectStreamException;

И когда мы попытаемся сериазовать объект - java вызовет эти методы! И будет писать/читать в поток уже с помощью них, поэтому использовать эти методы надо только в случае, если нам необходимо четко и гибко контроллировать весь процесс сериализации/дессериализации. При этом обязательно вызывайте default-методы, которые обеспечат стандартную сериализацию, а потом уже вносите какие-то свои изменения.

В writeObject, например:

private void writeObject(java.io.ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject(); // default serialization
//our special serialization
}

Аналогично и тут:

В readObject, например:

private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject(); // default deserialization
//our special deserialization and using setters
}

Заметим, что все эти методы - private, что вполне логично, так как мы уже пишем сериализаццию только для этого нашего класса специально. Также если мы отнаследовались от класса, который сериализуемый, но по каким-то причинам не хотим, чтобы наш класс был серализуемый - мы можем сделать вот так в нашем классе:

private void writeObject(ObjectOutputStream out) throws IOException {
    throw new NotSerializableException("Uuups!");
}
private void readObject(ObjectInputStream in) throws IOException {
    throw new NotSerializableException("Uuups!");
}

Также стоит отметить еще и то, что поля помеченные static - не сериализуются. Поля помеченные transient также не сериализуются.

Выводы и советы:

  • Если родительский класс - Serializable - все дочерние классы - serializable.
  • Если дочерний класс Serializable, но родительский нет - поля родительского класса заполнятся как в дефолтном конструкторе, при этом он обязан быть!
  • Процессом serialization and deserialization можно управлять добавив специальные методы в класс.
  • При Serialization порядок записи: метаданные класса, метаданные родителя, данные родителя , данные класса.
  • При Deserialization порядок записи: метаданные родителя, метаданные класса, данные класса, данные родителя.
  • Если у нас есть поле-ссылка на не Serializable класс и это поле не null - мы словим ошибку.
  • Поля помеченные static - не сериализуются.
  • Прост в использовании - java.io.Serializable у всех сериализуемых объектов и все.
  • Не такой уж и быстрый способ.

Externalizable interface

Как мы и говорили в начале - второй подход к сериализации.

В отличии от java.io.Serializable в данном интерфейсе у нас два метода – writeExternal(ObjectOutput) иreadExternal(ObjectInput). Именно этими методами мы и сериализуем/десериализуем объект. В данном случае мы не пишем никаких метаданных!

Мы только вызываем наши методы - и сериализуем наш класс как нам хочется. Это гибкий путь. Мы сами полностью описываем как мы будем сериализовать класс, при этом не пишем никаких метаданных. По сути - у нас свой протокол сериализации тут мы создаем!

Как это работает? Прежде всего тут мы вызываем конструктор по умолчанию. Именно поэтому мы обязаны его иметь. Все дочерние классы также обязаны его иметь. После этого на новом объекте мы вызываем методы(readExternal) и все поля заполняются значениями. Если поле transient - оно обязано иметь значение по умолчанию.

Вывод и советы:

  • Если мы реализуем оба интерфейса - Externalizable и Serializable - то Externalizable имеет больший приоритет.
  • Externalizable более гибок, более быстрый.
  • Externalizable не пишем метаданные.
  • Можем сериализовывать Static поля, хотя это плохой подход.
  • Мы не можем десериализовать final поля класса в Externalizable - так как мы используем конструктор, хотя сериализуются они как обычно.

Также:

  • После десериализации мы обязаны проверить объект на корректность - и кинуть java.io.InvalidObjectException. Если что-то не так.
  • Будем испытывать проблемы с serialzie/deserialize Singleton объекта.

Serial Version UID

Хорошо бы еще иметь private static final long serialVersionUID поле. В этом поле хранится уникальный id version сериализованного класса. Вычисляется из полей класса, порядком их объявления, методами и т.д. Если мы меняем класс - мы меняем и id. Это поле записывается в поток сериализации, при десериализации объекта мы сравниваем id и если что-то не так - кидаем exception. Java строго рекомендует объявлять это поле. Если его не объявлять, то, по-моему это поле будет браться из hash code. Это гарантия того, что все прошло правильно.

####Вывод: В целом, я бы советовал использовать стандартный подход, если не требуется какой-то специальной гибкости и супер-быстродействия.

##XML Serialization

Введение

Сериализовать можно также и в файл, например, в xml. Как можно это сделать? Есть стандартная библиотека.

JAXB

Java Architecture for XML Binding.

Здесь мы уже можем пользоваться XML-schema(XSD)и сравнивать Java объект с XML-документом. Также это известно как marshalling и demarshalling процесс. JAXB может сам сгенерить XSD. Конфигурируется все с помощью annotations.

JAXB, как и говорилось выше, входит в стандартную библиотеку Java - и это еще один плюс!

  • Сначала кидаем аннотацию @XmlRootElement на главный элемент. У нас может быть только один root элемент.
  • После чего задаем accessor type - @XmlAccessorType(XmlAccessType.____) - тут заменить надо ___ на то, что надо, например - XmlAccessType.NONE.

О том, что можно подставить:

  • XmlAccessType.NONE - Сохраняем только те поля, которые аннотированы.
  • XmlAccessType.FILED - Сохраняем все, кроме transient и static.
  • XmlAccessType.PROPERTY - Все getters/setters пары и аннотированные поля.
  • XmlAccessType.PUBLIC - Все public поля и аннотированные поля.

Вроде все понятно. Двигаемся дальше.

  • Кидаем аннтоацию@XmlElement на все поля, которые нам нужны. Необязательно, чтобы эти поля имели getters and setters, ведь мы снова используем Reflection.

Если у нас collections - используем XmlElementWrapper аннотацию. Смотрим пример кода. //todo JAXB включает в себя еще кучу аннотаций, которые помогут сохранить объект как нам хочется, например, задать порядок записи в xml и т.д.

##JSON Serialization JSON неплохой выбор из-за отличного синтаксиса, большого количества библиотек, маленького размера. Для примера, возьмем библиотеку GSON .

####Почему GSON?

  • Судя по тестам - отлично работает с большим объемом данных.
  • Работаем без аннотаций и метаданных.По суди мы как бы просто говорим - хочу этот объект в JSON.
  • Порядок записи контролируем.
  • Работает с null.
  • Довольно просто использовать.

####Как использовать? Сначала создаем GSON Builder:

Gson gsonBuilder = new GsonBuilder().setPrettyPrinting().create();

Когда мы создаем наш Gson парсер - можем также вызвать еще методы для дополнительных возможностей, например:

  • excludeFieldsWithModifiers()
  • setDateFormat()

и так далее.

Отличный выбор, простой и мощный, без метаданных и аннотаций. Все собираем методами, отсюда - все прозрачно и ясно.

Example of usage

##CSV Serialization Наряду с записью в xml, json и прочее можно записывать еще в csv-файлы. Это простейший способ. По сути csv-файл - это файл, где значения разделены запятой(но не обязательно запятой, подойдет любой разделитель, например, |). Иногда в начале такого файла идет так называемый Header,где описываются поля. Пример такого файла:

Id, name, surname, age
1, Aleksandr, Kuchuk, 25

Несмотря на то, что кажется устаревшим такой подход он до сих пор применяется. Например для логов платежей. Для чтения и запись в такие файлы есть несколько библиотек, такие как opencv или apache commons csv. Приведу пример работы с последней:

Класс, объекты которого будем сериализовать: Student Пример парсинга и записи: Example Запуск(обратите внимание на то, как мы достаем ресурс): Start

Clone this wiki locally