← Назад к вопросам

Как работает сериализация в Java?

2.0 Middle🔥 191 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Сериализация в Java

Сериализация — это процесс преобразования объекта Java в поток байтов, который можно сохранить в файл, передать по сети или сохранить в БД. Десериализация — обратный процесс восстановления объекта из этого потока.

Механизм сериализации

Для сериализации объект должен реализовать интерфейс Serializable:

import java.io.*;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String name;
    private int age;
    private transient String password; // Не будет сериализовано
    
    public Person(String name, int age, String password) {
        this.name = name;
        this.age = age;
        this.password = password;
    }
}

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

1. Запись объекта в файл:

public void serializeObject(Person person, String filename) throws IOException {
    try (FileOutputStream fos = new FileOutputStream(filename);
         ObjectOutputStream oos = new ObjectOutputStream(fos)) {
        oos.writeObject(person); // Преобразуется в байты
        oos.flush();
    }
}

2. Чтение объекта из файла (десериализация):

public Person deserializeObject(String filename) throws IOException, ClassNotFoundException {
    try (FileInputStream fis = new FileInputStream(filename);
         ObjectInputStream ois = new ObjectInputStream(fis)) {
        return (Person) ois.readObject(); // Восстанавливается из байтов
    }
}

serialVersionUID — версионирование

serialVersionUID — это версионный идентификатор класса. Используется при десериализации для проверки совместимости:

public class Product implements Serializable {
    // Версия 1: базовые поля
    private static final long serialVersionUID = 1L;
    
    private String name;
    private double price;
    
    // Если класс изменился (добавили новое поле):
    // private String category; // Новое в версии 2
    
    // Если забыть обновить serialVersionUID,
    // десериализация старых объектов вызовет InvalidClassException
}

Контроль сериализации

1. Метод writeObject() — кастомизация сериализации:

public class SecureData implements Serializable {
    private static final long serialVersionUID = 1L;
    private String data;
    private transient String secretKey;
    
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject(); // Пишет обычные поля
        // Кастомная логика: шифруем данные перед сохранением
        oos.writeObject(encrypt(data, secretKey));
    }
    
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject(); // Читает обычные поля
        // Кастомная логика: расшифровываем данные
        String encrypted = (String) ois.readObject();
        data = decrypt(encrypted, secretKey);
    }
}

2. Метод readResolve() — контроль десериализации:

public class Singleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        return INSTANCE;
    }
    
    // Гарантирует, что при десериализации вернётся тот же экземпляр
    private Object readResolve() throws ObjectStreamException {
        return INSTANCE;
    }
}

Как работает сериализация внутри

Когда вызывается writeObject():

  1. Сбор метаданных: Java анализирует класс и его поля
  2. Сбор данных: Извлекаются значения всех полей (кроме transient)
  3. Запись заголовка: Пишется уникальный идентификатор потока, версия формата
  4. Рекурсивная сериализация: Все referenced объекты также сериализуются
  5. Запись байтов: Данные записываются в ObjectOutputStream

Граф объектов

Сериализация обрабатывает граф объектов — если один объект ссылается на другой, оба будут сериализованы:

public class Company implements Serializable {
    private String name;
    private List<Employee> employees; // Список сериализуется рекурсивно
}

public class Employee implements Serializable {
    private String name;
    private Company company; // Если и Employee, и Company сериализуемы
    // Java отследит, чтобы не создать циклические ссылки
}

Проблемы и решения

1. NotSerializableException:

// ОШИБКА: класс не реализует Serializable
public class NotSerializable {} // throws NotSerializableException

// РЕШЕНИЕ:
public class Serialized implements Serializable {}

2. Неопределённые поля при изменении класса:

// Была версия 1:
private static final long serialVersionUID = 1L; // Забыли обновить!
private String name;
private int age;

// Теперь версия 2 (добавили email):
private String email; // InvalidClassException!

3. Транзакционные поля (transient):

public class DatabaseConnection implements Serializable {
    private String url;
    private transient Connection conn; // Не сохраняется, переинициализируется
    
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        conn = createConnection(); // Пересоздаём подключение
    }
}

Альтернативы сериализации

  • JSON (быстрее, совместимость лучше): Jackson, Gson
  • Protocol Buffers (эффективнее по размеру)
  • XML (человекочитаемо)
// JSON вместо Java сериализации
import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(person); // Сериализация
Person restored = mapper.readValue(json, Person.class); // Десериализация

Производительность

Ява-сериализация достаточно медленная и создаёт большие файлы. Для modern приложений рекомендуется использовать JSON, Protocol Buffers или другие форматы, особенно при работе с микросервисами.