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

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

1. Вступление

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

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

Процесс сериализации не зависит от экземпляра, т.е. объекты могут быть сериализованы на одной платформе и десериализованы на другой. Classes that are eligible for serialization need to implement a special marker interfaceSerializable.

И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. Обратите внимание, чтоstatic fields belong to a class (as opposed to an object) and are not serialized. Также обратите внимание, что мы можем использовать ключевое слово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 серийной версии

The JVM associates a version (long) number with each serializable class. Он используется для проверки того, что сохраненные и загруженные объекты имеют одинаковые атрибуты и, следовательно, совместимы при сериализации.

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

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

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

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

private void writeObject(ObjectOutputStream out) throws IOException;

and

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 с настраиваемой сериализацией. Обратите внимание, что мы должны пометить несериализуемые атрибуты какtransient, чтобы избежатьNotSerializableException.

4. Заключение

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

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