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("Повідомлення надіслано!");
}
}
}
}
Як це працює:
- Спочатку запускається Server (він чекає на підключення).
- Потім запускається Client (він підключається до "localhost:5000").
- Клієнт серіалізує об’єкт Message і надсилає його через сокет.
- Сервер отримує потік байтів, десеріалізує його та друкує текст.
Тут ми використовуємо сокети (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: зміни структури класу після серіалізації.
Якщо ви зберегли об’єкт, а потім додали або видалили поле в класі, під час спроби десеріалізації виникне помилка або з’являться «дивні» значення. Детальніше — в наступних лекціях.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ