← Назад к вопросам

Что такое сериализация в Java? Как реализовать Serializable?

1.8 Middle🔥 251 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Сериализация в Java

Сериализация — это процесс преобразования объекта Java в последовательность байтов, которая может быть сохранена в файл, отправлена по сети или сохранена в БД. Десериализация — обратный процесс восстановления объекта из байтов.

Основной концепт

Объект в памяти → Сериализация → Поток байтов → Передача/Сохранение Поток байтов → Десериализация → Объект в памяти

Когда использовать сериализацию

  • Сохранение состояния — сохранить объект на диск
  • Передача по сети — отправить объект по TCP/HTTP
  • RMI — Remote Method Invocation
  • Кешивание — временно сохранить в памяти
  • JMS — обмен сообщениями

Реализация Serializable

1. Базовый пример

import java.io.*;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    private Long id;
    private String name;
    private int age;
    private transient String password;

    public User(Long id, String name, int age, String password) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name=" + name +
                ", age=" + age +
                ", password=" + password +
                "}";
    }
}

2. Сохранение объекта (Serialization)

public class SerializationExample {
    public static void main(String[] args) throws IOException {
        User user = new User(1L, "Alice", 25, "secret123");

        try (FileOutputStream fos = new FileOutputStream("user.dat");
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeObject(user);
            System.out.println("Объект успешно сохранён");
        }
    }
}

3. Загрузка объекта (Deserialization)

public class DeserializationExample {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        try (FileInputStream fis = new FileInputStream("user.dat");
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            User user = (User) ois.readObject();
            System.out.println("Объект успешно загружен:");
            System.out.println(user);
        }
    }
}

Ключевое слово transient

public class SensitiveData implements Serializable {
    private String username;
    private transient String password;
    private transient List<String> tokens;
    private String email;
}

Keyword transient исключает поле из сериализации для:

  • Конфиденциальных данных
  • Несериализуемых объектов (File, Stream)
  • Временных данных

serialVersionUID - версионирование

серialVersionUID используется для проверки совместимости версий:

public class Product implements Serializable {
    private static final long serialVersionUID = 1L;

    private Long id;
    private String name;
    private double price;
}

При изменении класса увеличивай ID:

  • Совпадает ID → десериализация успешна
  • Не совпадает → java.io.InvalidClassException

Полный пример с коллекцией

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

public class UserService {
    public void saveUsers(List<User> users, String filename) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(filename);
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeInt(users.size());
            for (User user : users) {
                oos.writeObject(user);
            }
            System.out.println("Сохранено " + users.size() + " пользователей");
        }
    }

    public List<User> loadUsers(String filename) throws IOException, ClassNotFoundException {
        List<User> users = new ArrayList<>();
        try (FileInputStream fis = new FileInputStream(filename);
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            int count = ois.readInt();
            for (int i = 0; i < count; i++) {
                users.add((User) ois.readObject());
            }
            System.out.println("Загружено " + users.size() + " пользователей");
        }
        return users;
    }
}

Пользовательская сериализация

Для полного контроля используй readObject() и writeObject():

public class CustomUser implements Serializable {
    private static final long serialVersionUID = 1L;

    private Long id;
    private String name;
    private int age;
    private transient String password;

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        oos.writeObject(password != null ? password.length() : 0);
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        int passwordLength = (int) ois.readObject();
        System.out.println("Пароль имел длину: " + passwordLength);
    }
}

Externalizable - полный контроль

public class ExternalizableUser implements Externalizable {
    private Long id;
    private String name;
    private int age;

    public ExternalizableUser() {}

    public ExternalizableUser(Long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeLong(id);
        out.writeObject(name);
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.id = in.readLong();
        this.name = (String) in.readObject();
        this.age = in.readInt();
    }
}

Проблемы и опасности

Несериализуемые поля

public class BadExample implements Serializable {
    private FileReader file;    // ✗ НЕ сериализуем!
    private Thread thread;      // ✗ НЕ сериализуем!
}

// Ошибка: java.io.NotSerializableException
// Решение: используй transient
public class GoodExample implements Serializable {
    private transient FileReader file;
    private transient Thread thread;
}

Сравнение сериализации с JSON

Сериализуемость Java:

  • Размер: большой (бинарный)
  • Человекочитаемость: нет
  • Безопасность: опасна (код в объектах)
  • Совместимость: только Java
  • Производительность: быстрее

JSON:

  • Размер: меньше (текстовой)
  • Человекочитаемость: да
  • Безопасность: безопаснее
  • Совместимость: все языки
  • Производительность: медленнее

JSON альтернатива с Jackson

import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonExample {
    public static void main(String[] args) throws Exception {
        User user = new User(1L, "Alice", 25, "secret");
        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(user);
        System.out.println(json);

        User restored = mapper.readValue(json, User.class);
        System.out.println(restored);
    }
}

Тестирование

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.io.*;

public class SerializationTest {
    @Test
    public void testSerialization() throws IOException, ClassNotFoundException {
        User original = new User(1L, "Alice", 25, "secret");

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(original);
        }

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        User restored;
        try (ObjectInputStream ois = new ObjectInputStream(bais)) {
            restored = (User) ois.readObject();
        }

        assertEquals(original.getId(), restored.getId());
        assertEquals(original.getName(), restored.getName());
        assertEquals(original.getAge(), restored.getAge());
        assertNull(restored.getPassword());
    }
}

Лучшие практики

  1. Всегда используй serialVersionUID

    private static final long serialVersionUID = 1L;
    
  2. Помечай несериализуемые поля как transient

    private transient Thread thread;
    
  3. Версионируй при изменениях класса

    • Новое поле → увеличь ID
    • Удалённое поле → увеличь ID
  4. Не полагайся на сериализацию для чувствительных данных

    • Используй шифрование
    • Используй JSON с маскированием
  5. Для REST API используй JSON вместо Java сериализации

Заключение

Сериализация — мощный инструмент для сохранения состояния объектов:

  1. Serializable — просто реализуй интерфейс
  2. serialVersionUID — для версионирования
  3. transient — для исключения полей
  4. Безопасность — используй только для доверенных данных
  5. JSON — часто лучший выбор для современных приложений