0Pricing
Java Academy · Урок

Пользовательская сериализация: 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 — локальная установка не требуется.

Все уроки этого курса

  1. Основы сериализации Java
  2. Поля transient и serialVersionUID
  3. Пользовательская сериализация: writeObject и readObject
  4. Современные альтернативы: JSON и Protocol Buffers
← Назад к Java Academy