Что такое сериализация
Сериализация превращает граф объектов в байтовое представление, которое можно сохранить или передать; десериализация восстанавливает объекты обратно. Это нужно для персистентности, кэшей, передачи по сети.
Встроенная Java serialization включается маркерным интерфейсом Serializable: помеченный объект (и весь достижимый от него граф) JVM умеет сохранять автоматически. Поля transient исключаются из стандартного сохранения (секреты, кэши, вычислимые значения), как и static-поля (они принадлежат классу, а не объекту). Тонкую настройку дают методы writeObject/readObject.
serialVersionUID
serialVersionUID — идентификатор версии класса в сериализованном виде. При десериализации JVM сравнивает UID из потока с UID локального класса; при несовпадении бросает InvalidClassException.
UID нужно задавать ЯВНО (private static final long serialVersionUID). Если не задать, компилятор вычислит его из структуры класса — и любое изменение (добавил метод, поменял модификатор) сменит UID и сломает чтение ранее сохранённых данных. Явный UID даёт контроль над совместимостью версий.
class UserSnapshot implements Serializable {
private static final long serialVersionUID = 1L;
private final long id;
private transient String temporaryToken; // не сериализуется
}
Почему Java serialization опасна
Это, пожалуй, главный практический тезис. Десериализация недоверенных данных — серьёзная брешь безопасности: при восстановлении графа выполняется код (readObject и конструкторы), и злоумышленник, подсунув специально собранную цепочку объектов (gadget chain), может добиться удалённого выполнения кода. Так возникали громкие RCE-уязвимости.
Плюс хрупкость: формат завязан на приватную структуру класса, что превращает её в часть публичного контракта и мешает рефакторингу. Поэтому современная рекомендация (в духе Effective Java) — избегать Java serialization, не реализовывать Serializable без необходимости, а если приходится принимать сериализованные данные — фильтровать типы через ObjectInputFilter (Java 9+) и никогда не десериализовать чужой ввод.
Externalizable
Externalizable — альтернатива внутри Java: вы полностью описываете формат сами в writeExternal/readExternal, получая контроль и над содержимым, и над производительностью.
Цена — ручная работа и риск рассинхронизации чтения с записью. На практике, если уж нужен полный контроль, чаще берут внешний формат, а не Externalizable.
Современные форматы
Для хранения и обмена выбирают форматы с явной схемой, а не привязку к Java-классам. JSON (Jackson/Gson) — человекочитаемый, кросс-язычный, удобный для API, но многословный и слабо типизированный. Бинарные схемные форматы Protobuf/Avro — компактные, со строгой схемой и продуманной эволюцией (добавление полей не ломает старых читателей) и кросс-язычные.
Выбор по требованиям: отлаживаемость и простота → JSON; компактность, производительность и строгий контракт между сервисами → Protobuf/Avro.
Как отвечать на собеседовании
Опишите механику (Serializable, граф объектов, transient/static исключаются, writeObject/readObject) и роль явного serialVersionUID (иначе любое изменение класса ломает чтение). Сильный акцент — на безопасности: десериализация недоверенных данных → RCE через gadget chains, поэтому Java serialization избегают, а ввод фильтруют (ObjectInputFilter). Завершите выбором форматов: JSON vs Protobuf/Avro по схеме/компактности/эволюции.