Введение в сериализацию Java

1. Введение

Сериализация - это преобразование состояния объекта в поток байтов; десериализация делает наоборот. Другими словами, сериализация - это преобразование объекта Java в статический поток (последовательность) байтов, который затем может быть сохранен в базе данных или передан по сети.

2. Сериализация и десериализация

Процесс сериализации не зависит от экземпляра, т.е. объекты могут быть сериализованы на одной платформе и десериализованы на другой. Классы, подходящие для сериализации, должны реализовать специальный интерфейс маркера Serializable.

И ObjectInputStream, и ObjectOutputStream - это классы высокого уровня, которые расширяют java.io.InputStream и java.io.OutputStream соответственно. ObjectOutputStream может записывать примитивные типы и графики объектов в OutputStream в виде потока байтов. Эти потоки впоследствии можно прочитать с помощью ObjectInputStream .

Самый важный метод в ObjectOutputStream :

public final void writeObject(Object o) throws IOException;

Которая принимает сериализуемый объект и преобразует его в последовательность (поток) байтов. Точно так же самый важный метод в ObjectInputStream :

public final Object readObject() throws IOException, ClassNotFoundException;

Который может читать поток байтов и преобразовывать его обратно в объект Java. Затем его можно вернуть к исходному объекту.

Проиллюстрируем сериализацию с помощью класса Person . Обратите внимание, что статические поля принадлежат классу (в отличие от объекта) и не сериализуются . Также обратите внимание, что мы можем использовать ключевое слово transient, чтобы игнорировать поля класса во время сериализации:

public class Person implements Serializable { private static final long serialVersionUID = 1L; static String country = "ITALY"; private int age; private String name; transient int height; // getters and setters }

В приведенном ниже тесте показан пример сохранения объекта типа Person в локальный файл, а затем считывания этого значения обратно:

@Test public void whenSerializingAndDeserializing_ThenObjectIsTheSame() () throws IOException, ClassNotFoundException { Person person = new Person(); person.setAge(20); person.setName("Joe"); FileOutputStream fileOutputStream = new FileOutputStream("yourfile.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(person); objectOutputStream.flush(); objectOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("yourfile.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Person p2 = (Person) objectInputStream.readObject(); objectInputStream.close(); assertTrue(p2.getAge() == p.getAge()); assertTrue(p2.getName().equals(p.getName())); }

Мы использовали ObjectOutputStream для сохранения состояния этого объекта в файл с помощью FileOutputStream . Файл «yourfile.txt» создается в каталоге проекта. Затем этот файл загружается с помощью FileInputStream. ObjectInputStream берет этот поток и преобразует его в новый объект с именем p2 .

Наконец, мы проверяем состояние загруженного объекта, и оно соответствует состоянию исходного объекта.

Обратите внимание, что загруженный объект должен быть явно приведен к типу Person .

3. Предостережения относительно сериализации Java

Есть некоторые предостережения, касающиеся сериализации в Java.

3.1. Наследование и состав

Когда класс реализует интерфейс java.io.Serializable , все его подклассы также являются сериализуемыми. Напротив, когда объект имеет ссылку на другой объект, эти объекты должны реализовывать интерфейс Serializable отдельно, иначе будет выброшено NotSerializableException :

public class Person implements Serializable { private int age; private String name; private Address country; // must be serializable too } 

Если одно из полей в сериализуемом объекте состоит из массива объектов, тогда все эти объекты также должны быть сериализуемыми, иначе будет создано исключение NotSerializableException .

3.2. UID серийной версии

JVM связывает номер версии ( длинный ) с каждым сериализуемым классом. Он используется для проверки того, что сохраненные и загруженные объекты имеют одинаковые атрибуты и, следовательно, совместимы при сериализации.

Этот номер может генерироваться автоматически большинством IDE и основан на имени класса, его атрибутах и ​​связанных модификаторах доступа. Любые изменения приводят к другому числу и могут вызвать InvalidClassException .

Если сериализуемый класс не объявляет serialVersionUID , JVM автоматически сгенерирует его во время выполнения. Однако настоятельно рекомендуется, чтобы каждый класс объявлял свой serialVersionUID, поскольку сгенерированный зависит от компилятора и, таким образом, может привести к неожиданным InvalidClassExceptions .

3.3. Пользовательская сериализация в Java

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

private void writeObject(ObjectOutputStream out) throws IOException;

и

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;

С помощью этих методов мы можем сериализовать эти несериализуемые атрибуты в другие формы, которые можно сериализовать:

public class Employee implements Serializable { private static final long serialVersionUID = 1L; private transient Address address; private Person person; // setters and getters private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); oos.writeObject(address.getHouseNumber()); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); Integer houseNumber = (Integer) ois.readObject(); Address a = new Address(); a.setHouseNumber(houseNumber); this.setAddress(a); } }
public class Address { private int houseNumber; // setters and getters }

Следующий модульный тест проверяет эту настраиваемую сериализацию:

@Test public void whenCustomSerializingAndDeserializing_ThenObjectIsTheSame() throws IOException, ClassNotFoundException { Person p = new Person(); p.setAge(20); p.setName("Joe"); Address a = new Address(); a.setHouseNumber(1); Employee e = new Employee(); e.setPerson(p); e.setAddress(a); FileOutputStream fileOutputStream = new FileOutputStream("yourfile2.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(e); objectOutputStream.flush(); objectOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("yourfile2.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Employee e2 = (Employee) objectInputStream.readObject(); objectInputStream.close(); assertTrue( e2.getPerson().getAge() == e.getPerson().getAge()); assertTrue( e2.getAddress().getHouseNumber() == e.getAddress().getHouseNumber()); }

В этом коде мы видим, как сохранить некоторые несериализуемые атрибуты путем сериализации Address с пользовательской сериализацией. Обратите внимание, что мы должны пометить несериализуемые атрибуты как временные, чтобы избежать NotSerializableException.

4. Вывод

В этом кратком руководстве мы рассмотрели сериализацию Java, обсудили важные вещи, о которых следует помнить, и показали, как выполнять настраиваемую сериализацию.

Как всегда, исходный код, используемый в этом руководстве, доступен на GitHub.