JavaRush /Курсы /JAVA 25 SELF /Введение в сериализацию объектов: зачем нужна

Введение в сериализацию объектов: зачем нужна

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

1. Зачем нужна сериализация

Представьте, что ваш объект — это вещи, которые вы берёте с собой в отпуск. Сериализация — это упаковка всего содержимого чемодана в специальный контейнер, который можно положить в багаж или отправить по почте. Десериализация, соответственно, — это распаковка этого контейнера и получение вещей в исходном виде.

По сути, сериализация превращает объект в поток байтов, который можно сохранить в файл, передать по сети или просто хранить в памяти. Десериализация делает обратное: восстанавливает объект из этого потока. Если совсем упрощать, сериализация — это как «заморозка» объекта, чтобы потом «разморозить» его и получить обратно в том же состоянии.

Сохранение состояния объектов между запусками программы

Один из самых частых сценариев — это сохранение состояния программы. Например, у вас есть список пользователей, результаты игры или настройки приложения. Всё это удобно хранить прямо в виде объектов. Чтобы данные не терялись между запусками, их сериализуют в файл, а при следующем запуске — десериализуют.

Хороший пример — обычный сейв в игре. Когда игрок проходит уровень, его прогресс «замораживается» и записывается в файл с помощью сериализации. На следующий день он запускает игру, и прогресс «размораживается»: данные из файла превращаются обратно в объекты, и игрок продолжает с того же места, где остановился.

Давайте создадим такой простой сейв:

import java.io.*;

// Класс игрока должен быть Serializable
class Player implements Serializable {
    String name;
    int score;

    Player(String name, int score) {
        this.name = name;
        this.score = score;
    }
}

public class GameSaveExample {
    public static void main(String[] args) throws Exception {
        // Создаём объект игрока
        Player player = new Player("Ihor", 1500);

        // --- Сохранение (сериализация) ---
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("save.dat"))) {
            out.writeObject(player);
            System.out.println("Прогресс сохранён!");
        }

        // --- Загрузка (десериализация) ---
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("save.dat"))) {
            Player loaded = (Player) in.readObject();
            System.out.println("Прогресс загружен: " + loaded.name + " с очками " + loaded.score);
        }
    }
}

Обратите внимание: чтобы этот код работал, класс Player должен реализовывать интерфейс Serializable. Подробнее о нём — в следующей лекции!

  • Player — обычный класс с полями имя и очки, помеченный интерфейсом Serializable (implements Serializable).
  • ObjectOutputStream пишет объект в файл "save.dat".
  • ObjectInputStream читает этот же объект обратно.
  • В результате у нас получается настоящий сейв: при следующем запуске программа загрузит объект игрока с тем же состоянием.

Передача объектов по сети и между JVM

В распределённых системах часто требуется передавать объекты между разными программами или даже разными машинами. Например, у вас есть клиент и сервер, которые должны обмениваться сообщениями. Сериализация позволяет «упаковать» объект на одной стороне, отправить его по сети и «распаковать» на другой стороне.

Пример: Клиент отправляет серверу объект заказа (Order), сервер его получает, десериализует и обрабатывает.

Использование в технологиях Java

  • RMI (Remote Method Invocation): позволяет вызывать методы удалённых объектов — сериализация нужна для передачи аргументов и возвращаемых значений.
  • HTTP-сессии: в сервлетах объекты в сессии сериализуются при перезапуске контейнера.
  • JMS (Java Message Service): сообщения между компонентами могут быть сериализованы.
  • Кэширование: объекты могут сериализоваться для хранения в кэше (на диск или в распределённое хранилище).

Кэширование и переносимость

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

2. Примеры сценариев использования сериализации

Сохранение коллекции пользователей в файл

Допустим, у вас есть класс User:

public class User {
    String name;
    int age;
    // ... другие поля
}

И у вас есть список пользователей:

List<User> users = new ArrayList<>();
users.add(new User("Вася", 25));
users.add(new User("Маша", 30));
// ... и так далее

Чтобы сохранить этот список в файл, вы сериализуете его. Когда потребуется — десериализуете и получаете тот же самый список с теми же пользователями. Напомним, что класс User (и все его поля) должен поддерживать сериализацию, то есть реализовывать Serializable.

Передача сообщения между клиентом и сервером

Классический пример — чат. Пользователь пишет сообщение, объект Message сериализуется и отправляется по сети. Сервер получает поток байтов, десериализует объект, обрабатывает его и, возможно, пересылает дальше.

import java.io.*;
import java.net.*;

// Сообщение должно быть Serializable
class Message implements Serializable {
    String text;

    Message(String text) {
        this.text = text;
    }
}

// Сервер
class Server {
    public static void main(String[] args) throws Exception {
        try (ServerSocket serverSocket = new ServerSocket(5000)) {
            System.out.println("Сервер ждёт подключения...");
            Socket socket = serverSocket.accept();
            System.out.println("Клиент подключился!");

            try (ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) {
                Message msg = (Message) in.readObject();
                System.out.println("Получено сообщение: " + msg.text);
            }
        }
    }
}

// Клиент
class Client {
    public static void main(String[] args) throws Exception {
        try (Socket socket = new Socket("localhost", 5000)) {
            try (ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream())) {
                Message msg = new Message("Привет, сервер!");
                out.writeObject(msg);
                System.out.println("Сообщение отправлено!");
            }
        }
    }
}

Как это работает:

  1. Сначала запускается Server (он ждёт подключения).
  2. Потом запускается Client (он подключается к "localhost:5000").
  3. Клиент сериализует объект Message и отправляет его через сокет.
  4. Сервер получает поток байтов, десериализует его и печатает текст.

Здесь мы используем сокеты (ServerSocket, Socket) — это механизм сетевого взаимодействия, который вы будете изучать позже. Важно сейчас не детали работы сети, а сама идея: клиент создаёт объект Message, сериализует его и отправляет; сервер получает поток байтов, десериализует обратно в объект и печатает сообщение. Таким образом, даже если пока непонятно, что за классы ServerSocket и Socket, пример показывает ценность сериализации: благодаря ей можно «упаковать» объект, передать его по сети, а на другой стороне распаковать без лишних преобразований.

Кэширование объектов

В больших приложениях часто используют кэширование для ускорения работы. Например, результаты сложных вычислений сериализуются и сохраняются в кэш (файл, база данных, распределённое хранилище). При следующем запросе результат можно быстро восстановить, десериализовав объект.

import java.io.*;

// Результат вычислений, который мы хотим кэшировать
class Result implements Serializable {
    int value;

    Result(int value) {
        this.value = value;
    }
}

public class CacheExample {
    private static final String CACHE_FILE = "cache.dat";

    public static void main(String[] args) throws Exception {
        Result result;

        // Проверяем, есть ли кэш
        File file = new File(CACHE_FILE);
        if (file.exists()) {
            // Загружаем результат из кэша
            try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
                result = (Result) in.readObject();
                System.out.println("Загружено из кэша: " + result.value);
            }
        } else {
            // «Тяжёлое» вычисление (для примера просто квадрат числа)
            int x = 12345;
            System.out.println("Считаем... (это долго)");
            result = new Result(x * x);

            // Сохраняем результат в кэш
            try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) {
                out.writeObject(result);
                System.out.println("Сохранено в кэш: " + result.value);
            }
        }
    }
}

3. Ограничения и риски сериализации

Сериализация — мощный инструмент, но не без подводных камней. Давайте обсудим основные ограничения и риски.

Не все объекты можно сериализовать

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

Пример: Класс с полем типа FileInputStream нельзя сериализовать — при попытке сериализации возникнет ошибка.

Вопросы безопасности

Сериализация — это потенциальная дыра в безопасности. Если вы десериализуете данные, полученные из ненадёжного источника (например, из интернета), злоумышленник может подложить вредоносный поток байтов, который приведёт к неожиданному поведению вашей программы, а иногда — даже к выполнению вредоносного кода.

Правило: Никогда не десериализуйте данные из недоверенных источников! Это как принимать посылку от неизвестного отправителя — внутри может быть что угодно.

Совместимость версий

Если вы изменили структуру класса (например, добавили или удалили поле), сериализованные ранее объекты могут стать несовместимыми с новой версией класса. Это может привести к ошибкам при десериализации. Подробнее этот вопрос будет рассмотрен в следующих лекциях.

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

Бинарная сериализация в Java достаточно быстрая, но иногда не самая компактная и не всегда удобная для обмена с другими языками программирования. Для обмена с внешними системами часто используют текстовые форматы (JSON, XML).

4. Типичные ошибки при первом знакомстве с сериализацией

Ошибка №1: попытка сериализовать объект, который не реализует интерфейс Serializable.
В результате получите исключение NotSerializableException. Не забывайте явно указывать implements Serializable в классе и следить, чтобы все поля тоже были сериализуемыми!

Ошибка №2: сериализация объектов с несериализуемыми полями.
Если ваш класс содержит поле типа, который не поддерживает сериализацию (например, поток или соединение с БД), сериализация не сработает. Решение — пометить такие поля как transient (об этом позже).

Ошибка №3: десериализация данных из ненадёжных источников.
Это может привести к уязвимостям безопасности или даже к выполнению вредоносного кода. Доверяйте только тем данным, которые были сериализованы вашей программой!

Ошибка №4: изменения структуры класса после сериализации.
Если вы сохранили объект, а потом добавили или удалили поле в классе, при попытке десериализации возникнет ошибка или появятся «странные» значения. Подробнее — в следующих лекциях.

1
Задача
JAVA 25 SELF, 42 уровень, 0 лекция
Недоступна
Сжатие строки в ZIP-архив
Сжатие строки в ZIP-архив
1
Задача
JAVA 25 SELF, 42 уровень, 0 лекция
Недоступна
Извлечение файла из ZIP-архива
Извлечение файла из ZIP-архива
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
kasnil Уровень 8
9 февраля 2026
Как задачи с cериализаций связаны?😄
Andrey Уровень 1
8 октября 2025
42