JavaRush /Курсы /JAVA 25 SELF /Сериализация вложенных объектов: особенности

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

JAVA 25 SELF
44 уровень , 0 лекция
Открыта

1. Введение: сериализация объектов с полями-ссылками

В реальных приложениях редко встречаются полностью «плоские» классы. Обычно объект содержит другие объекты, а те, в свою очередь, могут содержать ещё что-то. Это называется композицией (или вложенностью) объектов. Например:

public class Address {
    String city;
    String street;
}

public class Person {
    String name;
    int age;
    Address address; // Вложенный объект!
}

Когда мы сериализуем такой объект, возникает вопрос: а что делать с полем address? Должна ли Java сериализовать его вместе с Person? А если у Address внутри ещё какой-то объект? К счастью (или к сожалению — зависит от ситуации), Java по умолчанию сериализует все вложенные объекты рекурсивно, если они тоже реализуют интерфейс Serializable.

Сериализация в Java — это всегда глубокая сериализация (deep serialization). Это значит, что сериализуется не только сам объект, но и все объекты, на которые он ссылается через свои (не transient) поля, и так далее — до самого «дна».

Визуализация процесса

graph TD A[Person] --> B[Address] B --> C[CityInfo] A --> D[Pet]

Словом, если решили сериализовать Person, то Java сериализует и Address, и всё, что внутри Address, и так далее.

2. Требования к вложенным объектам: Serializable обязателен!

Вы уже, вероятно, обратили внимание на важный нюанс: все вложенные объекты, которые сериализуются, тоже должны реализовывать интерфейс Serializable.

Если хотя бы одно поле-ссылка указывает на объект, который не реализует Serializable, попытка сериализации закончится исключением java.io.NotSerializableException.

Давайте рассмотрим примеры глубокой сериализации.

Пример: всё хорошо

import java.io.Serializable;

public class Address implements Serializable {
    String city;
    String street;
}

public class Person implements Serializable {
    String name;
    int age;
    Address address;
}

Оба класса реализуют Serializable. Всё работает, сериализация проходит успешно.

Пример: ошибка!

public class Address { // НЕ реализует Serializable!
    String city;
    String street;
}

public class Person implements Serializable {
    String name;
    int age;
    Address address;
}

Как видим, здесь Address не реализует интерфейс Serializable. Так что при сериализации Person мы получим исключение NotSerializableException.

3. Пример: сериализация и десериализация с вложенным объектом

Давайте посмотрим, как это выглядит на практике. На прошлом уровне мы работали с приложением «Менеджер контактов». Давайте теперь добавим в него адрес каждого пользователя.

import java.io.*;

class Address implements Serializable {
    String city;
    String street;

    Address(String city, String street) {
        this.city = city;
        this.street = street;
    }
}

class Person implements Serializable {
    String name;
    int age;
    Address address;

    Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
}

public class SerializationDemo {
    public static void main(String[] args) throws Exception {
        Person p = new Person("Иван", 30, new Address("Прага", "Славинска, 1"));
        // Сериализация
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
        out.writeObject(p);
        out.close();

        // Десериализация
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"));
        Person restored = (Person) in.readObject();
        in.close();

        System.out.println(restored.name + ", " + restored.age + ", " +
                           restored.address.city + ", " + restored.address.street);
    }
}

Результат:

Иван, 30, Прага, Славинска, 1

Всё работает: вложенный объект Address сериализован и восстановлен вместе с Person.

4. Что если вложенный объект не сериализуем?

Если попытаться сериализовать объект, у которого хотя бы одно поле-ссылка указывает на объект, не реализующий Serializable, Java выбросит исключение уже при попытке сериализации.

Демонстрация ошибки

class Address { // не Serializable!
    String city;
    String street;
}

class Person implements Serializable {
    String name;
    Address address;
}

public class Test {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        p.name = "Петя";
        p.address = new Address();
        p.address.city = "Дерри";
        p.address.street = "Вязов";
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
        out.writeObject(p); // <-- БУДЕТ ИСКЛЮЧЕНИЕ!
        out.close();
    }
}

Ошибка:

java.io.NotSerializableException: Address

5. Transient для вложенных объектов

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

Пример с transient

class Address { // не Serializable
    String city;
    String street;
}

class Person implements Serializable {
    String name;
    transient Address address; // transient!

    Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Address addr = new Address();
        addr.city = "Лос-Сантос";
        addr.street = "Малхолланд Драйв";
        Person p = new Person("Саша", addr);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
        out.writeObject(p);
        out.close();

        // Десериализация
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"));
        Person restored = (Person) in.readObject();
        in.close();

        System.out.println(restored.name); // "Саша"
        System.out.println(restored.address); // null!
    }
}

Результат:
Поле address после десериализации равно null, потому что оно transient.

6. Глубокая вложенность и рекурсия

Сериализация работает рекурсивно: если у Person есть поле Address, а у Address есть поле CityInfo, и так далее, сериализатор будет «нырять» всё глубже, пока не встретит что-то несериализуемое или пока не закончится память (шутка, но только наполовину).

Важно: циклические ссылки

Java-сериализатор умеет работать с циклическими ссылками. Если, например, у объекта есть ссылка на другой объект, который в свою очередь ссылается обратно, сериализатор не зациклится, а аккуратно сохранит структуру.

class A implements Serializable {
    B b;
}
class B implements Serializable {
    A a;
}

Если создать объекты A и B, которые ссылаются друг на друга, сериализация не вызовет StackOverflowError — Java помнит уже сериализованные объекты.

7. Пример: сериализация объекта с вложенным списком

Часто внутри объекта есть коллекции других объектов. Например, у пользователя может быть список друзей:

import java.io.*;
import java.util.*;

class Person implements Serializable {
    String name;
    List<Person> friends;

    Person(String name) {
        this.name = name;
        this.friends = new ArrayList<>();
    }
}

public class FriendsSerialization {
    public static void main(String[] args) throws Exception {
        Person alice = new Person("Alice");
        Person bob = new Person("Bob");
        alice.friends.add(bob);

        // Сериализация
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("friends.ser"));
        out.writeObject(alice);
        out.close();

        // Десериализация
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("friends.ser"));
        Person restored = (Person) in.readObject();
        in.close();

        System.out.println(restored.name); // Alice
        System.out.println(restored.friends.get(0).name); // Bob
    }
}

Важно: Коллекции стандартной библиотеки Java (ArrayList, HashMap и др.) реализуют Serializable, так что всё работает «из коробки».

8. Типичные ошибки при сериализации вложенных объектов

Ошибка №1: Один из вложенных объектов не реализует Serializable. Вы забыли добавить implements Serializable в один из вложенных классов. Итог — NotSerializableException при первой попытке сериализации. Проверяйте, что все классы в вашей цепочке сериализации поддерживают этот интерфейс.

Ошибка №2: Несериализуемое поле не объявлено transient. Если у вас есть поле, которое не сериализуется (например, поток, соединение с базой, что-то временное), но вы не объявили его как transient, сериализация упадёт с ошибкой. Не забывайте про transient!

Ошибка №3: Несовпадение serialVersionUID во вложенных классах. Если вы явно объявляете serialVersionUID во вложенных классах и меняете их структуру, не забывайте обновлять этот идентификатор — иначе возможны ошибки при десериализации.

Ошибка №4: Мутирующие (изменяемые) вложенные объекты. Если вы сериализуете коллекцию или объект, который потом меняется (например, список друзей), после десериализации это уже будет «снимок» на момент сериализации. Новые изменения в оригинале никак не отразятся в десериализованном объекте.

Ошибка №5: Сериализация огромных графов объектов. Если у вас очень сложная структура с множеством вложенных объектов, сериализация может занять много времени и памяти. Иногда лучше сериализовать только ключевые данные, а не всю структуру целиком.

1
Задача
JAVA 25 SELF, 44 уровень, 0 лекция
Недоступна
Цифровая картотека личных данных 🏢
Цифровая картотека личных данных 🏢
1
Задача
JAVA 25 SELF, 44 уровень, 0 лекция
Недоступна
Управление сложной логистикой доставки: глубоко вложенные данные 🌍
Управление сложной логистикой доставки: глубоко вложенные данные 🌍
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Andrey Уровень 1
9 октября 2025
44