Пользовательская сериализация: writeObject и readObject
Переопределяйте writeObject и readObject для шифрования, сжатия или специальной обработки полей.
«Пользовательская сериализация: writeObject и readObject» — бесплатный урок Java Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.
Когда стандартной сериализации недостаточно
Стандартная сериализация сохраняет все поля, не помеченные как transient, без изменений. Иногда требуются шифрование, сжатие или другое представление данных. В таких случаях используются хуки writeObject и readObject.
Объявление writeObject
Добавьте метод private void writeObject(ObjectOutputStream oos). Java вызовет его вместо стандартного механизма. Вы будете точно управлять тем, что записывается.
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // write non-transient fields normally
oos.writeUTF(encrypt(this.secret)); // write encrypted secret
}Объявление readObject
Добавьте метод private void readObject(ObjectInputStream ois). Java вызовет его во время десериализации. Считывайте данные точно в том же порядке, в каком записывали их.
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // restore non-transient fields
this.secret = decrypt(ois.readUTF()); // decrypt and restore
}Порядок имеет значение
Всё, что записывается в writeObject, должно считываться обратно в том же порядке в readObject. Несовпадение порядка вызывает StreamCorruptedException.
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.writeInt(version);
oos.writeUTF(data);
}
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
this.version = ois.readInt(); // same order
this.data = ois.readUTF();
}Сжатие данных при сериализации
Оберните поток в GZIPOutputStream внутри writeObject, чтобы сжать большие поля, а затем используйте GZIPInputStream внутри readObject для распаковки.
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (GZIPOutputStream gz = new GZIPOutputStream(bos)) {
gz.write(largePayload.getBytes());
}
oos.writeObject(bos.toByteArray());
}Хук readResolve
readResolve() вызывается после десериализации. Верните заменяющий объект — это полезно для одиночек и перечислений, чтобы после десериализации существовал только один экземпляр.
public class AppConfig implements Serializable {
private static final AppConfig INSTANCE = new AppConfig();
private AppConfig() {}
public static AppConfig getInstance() { return INSTANCE; }
private Object readResolve() { return INSTANCE; } // return singleton
}Хук writeReplace
writeReplace() вызывается перед сериализацией. Верните другой объект, который будет сериализован вместо исходного, — это полезно для шаблонов прокси и заместителей сериализации.
private Object writeReplace() {
return new UserProxy(this.id); // serialize a lightweight proxy
}Проверка полей в readObject
Внутри readObject можно добавить логику проверки, чтобы отклонять недопустимые или изменённые злоумышленником данные в рамках многоуровневой защиты.
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject();
if (age < 0 || age > 150) {
throw new InvalidObjectException("Invalid age: " + age);
}
}Обработка изменений версий
Используйте поле версии в writeObject, чтобы readObject мог корректно обрабатывать старый и новый форматы во время миграций.
private static final int SERIAL_VERSION = 2;
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeInt(SERIAL_VERSION);
if (SERIAL_VERSION >= 2) oos.writeUTF(email); // new field
}
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject();
int v = ois.readInt();
if (v >= 2) this.email = ois.readUTF();
}Шаблон прокси сериализации
Надёжный подход: создать закрытый статический внутренний класс, содержащий минимально необходимое состояние, затем использовать writeReplace для записи прокси, а его readResolve — для восстановления исходного объекта.
Особенности производительности
Пользовательская сериализация добавляет накладные расходы на вызовы методов, но может значительно уменьшить размер полезной нагрузки. Перед оптимизацией проведите измерения; для максимального контроля используйте Externalizable.
Быстрая проверка
Что должно быть верно для порядка действий в writeObject и readObject?
Итоги
Переопределяйте writeObject и readObject для шифрования, сжатия или пользовательских форматов. Используйте readResolve для одиночек. Всегда считывайте данные в том же порядке, в каком записываете их.
Часто задаваемые вопросы
Урок «Пользовательская сериализация: writeObject и readObject» бесплатный?
Да — полный текст урока «Пользовательская сериализация: writeObject и readObject» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.
Чему я научусь в уроке «Пользовательская сериализация: writeObject и readObject»?
Переопределяйте writeObject и readObject для шифрования, сжатия или специальной обработки полей. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Java Academy?
Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Пользовательская сериализация: writeObject и readObject»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Java Academy?
Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Основы сериализации Java
- Поля transient и serialVersionUID
- Пользовательская сериализация: writeObject и readObject
- Современные альтернативы: JSON и Protocol Buffers