Десериализация Процесс десериализации в Java

Java

1. Сериализуемый интерфейс

Сериализация и десериализация в Java — это взаимные методы, при которых сериализация может записывать объект Java в файловую систему для хранения или отправлять его по сети другим приложениям. Группа десериализации работает как раз наоборот и может восстанавливать (рефакторинг) сериализованные данные в объекты Java. Возможности сериализации могут быть достигнуты путем объявленияjava.io.Serializableинтерфейс для реализации. О том, имеет ли класс возможность сериализации, можно судить по двум условиям:

  • Объявляет ли этот класс реализациюjava.io.Serializableинтерфейс
  • Объявляет ли суперкласс этого класса реализациюjava.io.Serializableинтерфейс

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

class A{
    int field1;
    //getter and setter
}
class B extends A implements java.io.Serializable{
    //...
}

2. Примеры и результаты работы

Определите частный домен и защищенный домен отдельно в родительском классе пользователя:

public class UserParent {
    private String firstName;
    private String lastName;
    private int accountNumber;
    protected Date dateOpened;
    public UserParent(){
        System.out.println("UserParent default constructor called!");
    }
    //...
    //getter and setter
    @Override
    public String toString() {
        return "UserParent{" +
                "firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", accountNumber=" + accountNumber +
                ", dateOpened=" + dateOpened +
                '}';
    }
}

Объявить реализацию в классе Userjava.io.SerializableИнтерфейс и наследование UserParent, определение частных полей и настройка функций для сериализации и десериализацииwriteObject,readObject:

public class User extends UserParent implements Serializable {
    private static final long serialVersionUID = 7829136421241571165L;
    private String userVar;
    public User(){
        System.out.println("User default constructor called!");
    }

    public User(String firstName, String lastName, int accountNumber, Date dateOpened) {
        super.setFirstName(firstName);
        super.setLastName(lastName);
        super.setAccountNumber(accountNumber);
        super.setDateOpened(dateOpened);
    }
    private void writeObject(ObjectOutputStream objectOutputStream) throws IOException {
        objectOutputStream.writeUTF(super.getFirstName());
        objectOutputStream.writeUTF(super.getLastName());
        objectOutputStream.writeInt(super.getAccountNumber());
        objectOutputStream.writeLong(dateOpened.getTime());
        objectOutputStream.writeUTF(userVar);
    }
    private void readObject(ObjectInputStream objectInputStream) throws IOException {
        super.setFirstName(objectInputStream.readUTF());
        super.setLastName(objectInputStream.readUTF());
        super.setAccountNumber(objectInputStream.readInt());
        dateOpened = new Date(objectInputStream.readLong());
        userVar = objectInputStream.readUTF();
    }
    public void setUserVar(String userVar) {
        this.userVar = userVar;
    }

    @Override
    public String toString() {
        return "User{" + super.toString()+
                "userVar='" + userVar + '\'' +
                '}';
    }
}

Последний метод вызова:

public static void main(String[] args) throws IOException, ClassNotFoundException {
        String testData = "testData";
        int testNum = 1;
        Date testDate = new Date();
        User testUser = new User(testData,testData,testNum,testDate);
        testUser.setUserVar(testData);

        String name = "User.ser";
        FileOutputStream fileOut = new FileOutputStream(name);
        ObjectOutputStream objectOutputStream
                = new ObjectOutputStream(fileOut);
        objectOutputStream.writeObject(testUser);
        fileOut.close();
        System.out.println("testUser serializable completed! ");

        FileInputStream fileIn = new FileInputStream(name);
        ObjectInputStream objectInputStream
                = new ObjectInputStream(fileIn);
        User deserializableUser = (User)objectInputStream.readObject();
        fileIn.close();
        System.out.println(deserializableUser);

    }

Окончательный результат:

UserParent default constructor called!
testUser serializable completed! 
UserParent default constructor called!
User{UserParent{firstName='testData', lastName='testData', accountNumber=1, dateOpened=Sat Dec 28 15:50:00 CST 2019}userVar='testData'}

3. Анализ процесса выполнения экземпляра

Сначала в примере создается объект User. Перед вызовом конструктора User для инициализации значения конструктор User сначала вызывает конструктор UserParent, поэтому консоль выводитUserParent default constructor called!утверждение.

Затем пример сериализует объект testUser и сохраняет его сериализованные битовые данные в"User.ser"файл и распечататьtestUser serializable completed!утверждение . Примечание. При сериализации объекта Java тип объекта представляет собой метаданные класса, значение поля и тип преобразуются в ряд соответствующих битов и записываются в файл или передаются по сети, а затем метод десериализации передает эту информацию.Реконструировать объект.

Наконец, экземпляр десериализует и реконструирует объект из файла и выводит оставшиеся операторы вывода. Но почему конструктор без аргументов в UserParent вызывается вместо конструктора без аргументов в User?

4. Обратное последовательное лечение

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

To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype's public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class's state. It is an error to declare a class Serializable if this is not the case. The error will be detected at runtime.

Видно, что для реализации декларацииjava.io.SerializableИнтерфейс не наследуется без объявленной реализацииjava.io.SerializableПодкласс суперкласса интерфейса должен удовлетворять двум условиям:

  • Реализовать соответствующий метод сохранения и восстановления состояния объекта в подклассе
  • Нет заявленной реализацииjava.io.SerializableСуперкласс интерфейса должен иметь доступный конструктор без аргументов.

Точно так же, как создание экземпляра объекта подкласса, если при входе в конструктор подкласса нет явного объявления, конструктор родительского класса без параметров будет автоматически вставлен в первую строку до корня, то есть конструктор без параметров в классе Object (смотрите причину здесь3.4.4 Цепочка конструкторов и раздел конструктора по умолчанию). Десериализация соответствует описанному выше. Десериализация требует, чтобы все суперклассы объявили сериализуемый интерфейс. Если какой-либо суперкласс не имеет объявления интерфейса, он должен иметь конструктор без параметров. Поэтому в процессе десериализацииJVM проходит через суперкласс верхнего уровня от подкласса к себе.Если все суперклассы объявляют сериализуемый интерфейс, JVM в конечном итоге достигнет самого объектного класса и создаст экземпляр; если суперкласс не имеет объявления интерфейса, его конструктор по умолчанию вызывается в Создать экземпляр в памяти.

Итак, теперь у JVM есть экземпляр в памяти, использующий конструктор UserParent без аргументов, и после этого конструктор класса вызываться не будет. После этого JVM считывает данные в файле, чтобы установить информацию о типе и информацию о домене, принадлежащую testUser. После создания экземпляра JVM сначала устанавливает его статические поля, а затем внутренне вызывает метод readObject() по умолчанию (если он не переопределен, в противном случае вызывает переопределенный метод), который отвечает за восстановление состояния объекта. После завершения метода readObject() процесс десериализации завершается.

О том, почему конструктор User без аргументов не выполняется при десериализации, см. в официальной документации дляОписание метода readObject, пункт 11. Прежде всего, какова функция конструктора? Ответ. Конструктор инициализирует объектную переменную значением по умолчанию или значением, присвоенным внутри конструктора. Десериализованные данные уже содержат требуемое значение, и для восстановления значения вызывается метод readObject.

5. Резюме

1. В процессе десериализации JVM проходит через суперкласс верхнего уровня от подкласса к себе.Если все суперклассы объявляют сериализуемый интерфейс, JVM в конечном итоге достигнет класса объекта и создаст экземпляр; если она встретит суперкласс без объявление интерфейса вызывает конструктор по умолчанию для создания экземпляра в памяти

6. Некоторые другие важные вопросы

1. Частный домен, установленный в классе UserParent(firstName;lastName;accountNumber;)Не наследуется пользователем. видетьздесь:

Members of a class that are declared private are not inherited by subclasses of that class.

Only members of a class that are declared protected or public are inherited by subclasses declared in a package other than the one in which the class is declared.

Constructors, static initializers, and instance initializers are not members and therefore are not inherited.

Reference

【1】docs.Oracle.com/java-color/7/do…

【2】doc store.Mick.UA/или Elly/Java…

【3】как это сделать на java.com/Java/serial…

【4】docs.Oracle.com/java-color/7/do…

【5】docs.Oracle.com/JavaColor/spec…