Сериализация объектов в Java: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Сериализация объектов в Java: от основ до продвинутых техник

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

Основы: интерфейс Serializable

Чтобы объект можно было сериализовать, его класс должен реализовать маркерный интерфейс java.io.Serializable. Этот интерфейс не содержит методов — он лишь сообщает виртуальной машине, что объект разрешено сериализовать.

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }
}

Поле serialVersionUID — уникальный идентификатор версии класса. Если его не объявить явно, JVM вычислит его автоматически, но это может привести к InvalidClassException при десериализации, если класс изменился. Рекомендуется всегда задавать его вручную.

Процесс сериализации и десериализации

Для записи объекта в поток используется ObjectOutputStream, для чтения — ObjectInputStream. Рассмотрим базовый пример:

import java.io.*;

public class SerializationDemo {
    public static void main(String[] args) {
        User user = new User("Алексей", 30);

        // Сериализация
        try (FileOutputStream fos = new FileOutputStream("user.ser");
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeObject(user);
            System.out.println("Объект сериализован: " + user);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Десериализация
        try (FileInputStream fis = new FileInputStream("user.ser");
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            User restoredUser = (User) ois.readObject();
            System.out.println("Объект восстановлен: " + restoredUser);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

При десериализации конструктор объекта не вызывается — состояние восстанавливается напрямую из потока. Это важное отличие от обычного создания объекта через new.

Ключевое слово transient

Иногда нужно исключить некоторые поля из сериализации, например, пароли или временные вычисления. Для этого используется модификатор transient. Поля, помеченные им, при сериализации получают значения по умолчанию (0, null, false).

public class UserWithPassword implements Serializable {
    private static final long serialVersionUID = 1L;
    private String login;
    private transient String password; // не будет сериализовано

    public UserWithPassword(String login, String password) {
        this.login = login;
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{login='" + login + "', password='" + password + "'}";
    }
}

После десериализации поле password будет null. Это удобно для безопасности, но требует дополнительной логики восстановления.

Кастомная сериализация: методы writeObject и readObject

Если стандартное поведение не устраивает, можно переопределить приватные методы writeObject(ObjectOutputStream oos) и readObject(ObjectInputStream ois). Они вызываются автоматически во время сериализации/десериализации.

import java.io.*;

public class CustomUser implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private transient String hash;

    public CustomUser(String name, String hash) {
        this.name = name;
        this.hash = hash;
    }

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject(); // сериализуем обычные поля
        oos.writeObject(hash);    // пишем transient-поле вручную
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();  // восстанавливаем обычные поля
        this.hash = (String) ois.readObject(); // читаем хэш
    }

    @Override
    public String toString() {
        return "CustomUser{name='" + name + "', hash='" + hash + "'}";
    }
}

Метод defaultWriteObject() сериализует все не-transient поля, а defaultReadObject() восстанавливает их. Дополнительно мы пишем и читаем transient-поле, что позволяет сохранить его, но с контролем процесса.

Интерфейс Externalizable: полный контроль

Для

Рекомендаций