1. Вступ
У Java серіалізація працює лише з тими об’єктами, які це явно дозволяють. Для цього клас має реалізувати спеціальний інтерфейс — java.io.Serializable.
import java.io.Serializable;
public class Person implements Serializable {
// Поля, конструктори, методи
}
Serializable — це маркерний інтерфейс: у ньому немає методів, він просто повідомляє JVM: «цей клас можна серіалізувати, не бійтеся!». Якщо ви спробуєте серіалізувати об’єкт класу, який не реалізує Serializable, отримаєте виняток NotSerializableException. Навіть якщо принаймні одне поле (або вкладений об’єкт) не є серіалізовним — серіалізація не спрацює.
ObjectOutputStream і ObjectInputStream
- ObjectOutputStream — клас, що записує об’єкти в потік (наприклад, у файл або через мережу).
- ObjectInputStream — клас, що зчитує об’єкти з потоку.
Вони працюють у парі: один серіалізує об’єкт, інший — десеріалізує.
Основні методи
- writeObject(Object obj) — серіалізує об’єкт і записує його в потік.
- readObject() — зчитує об’єкт із потоку, десеріалізує та повертає його.
Важливо: обидва класи працюють поверх звичайних потоків введення-виведення (OutputStream і InputStream). Найчастіше їх використовують разом із файловими потоками — FileOutputStream і FileInputStream, але їх також можна застосовувати з мережевими потоками.
2. Приклад серіалізації
Напишімо простий приклад: серіалізуємо та десеріалізуємо об’єкт класу Person.
Крок 1. Описуємо клас
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
// поле transient не буде серіалізовано
private transient String secret;
public Person(String name, int age, String secret) {
this.name = name;
this.age = age;
this.secret = secret;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", secret='" + secret + "'}";
}
}
Крок 2. Серіалізація об’єкта у файл
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializeDemo {
public static void main(String[] args) throws Exception {
Person person = new Person("Alice", 30, "likes pizza");
// Створюємо потік для запису у файл
FileOutputStream fileOut = new FileOutputStream("person.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
// Зберігаємо об’єкт
out.writeObject(person);
// Закриваємо потоки
out.close();
fileOut.close();
System.out.println("Об’єкт серіалізовано у файл person.bin");
}
}
Крок 3. Десеріалізація об’єкта з файлу
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializeDemo {
public static void main(String[] args) throws Exception {
// Відкриваємо потік для читання з файлу
FileInputStream fileIn = new FileInputStream("person.bin");
ObjectInputStream in = new ObjectInputStream(fileIn);
// Відновлюємо об’єкт
Person person = (Person) in.readObject();
in.close();
fileIn.close();
System.out.println("Об’єкт десеріалізовано: " + person);
}
}
Очікуваний вивід
Об’єкт серіалізовано у файл person.bin
Об’єкт десеріалізовано: Person{name='Alice', age=30, secret='null'}
Увага! Поле transient не серіалізується. Після десеріалізації воно дорівнюватиме null. Це важливо для тимчасових або чутливих даних.
3. Обмеження та особливості
Усі поля мають бути серіалізовними
Якщо в класі є поля, які самі не реалізують Serializable (або містять такі об’єкти), серіалізація завершиться з помилкою. Наприклад, поля типу Thread або Socket зробити «просто так» серіалізовними не можна.
Статичні та transient-поля
- Статичні поля (static) не серіалізуються: вони належать класу, а не конкретному об’єкту.
- transient-поля — позначені як transient, явно виключаються з серіалізації і після відновлення отримують значення за замовчуванням (null, 0 тощо).
Винятки
- Спроба серіалізувати об’єкт, який не реалізує Serializable, викликає NotSerializableException.
- Під час десеріалізації можливі помилки: файл не знайдено, невідповідність класів, пошкоджені дані тощо.
Версії класів
Якщо ви зміните структуру класу після серіалізації (наприклад, додасте/видалите поля), під час десеріалізації може виникнути InvalidClassException. Для контролю версій використовується спеціальне поле serialVersionUID (про нього докладніше в одній із наступних лекцій).
4. Практика: серіалізація та десеріалізація об’єкта у файл
Припустімо, у нас є клас Person, і ми хочемо зберігати список людей у файл і читати його назад.
Клас Person (серіалізовний)
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
private transient String secret; // не буде серіалізовано
public Person(String name, int age, String secret) {
this.name = name;
this.age = age;
this.secret = secret;
}
@Override
public String toString() {
return name + " (" + age + "), secret: " + secret;
}
}
Серіалізація списку людей
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class SerializeListDemo {
public static void main(String[] args) throws Exception {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30, "likes pizza"));
people.add(new Person("Bob", 25, "hates broccoli"));
FileOutputStream fileOut = new FileOutputStream("people.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
// Зберігаємо список
out.writeObject(people);
out.close();
fileOut.close();
System.out.println("Список людей серіалізовано.");
}
}
Десеріалізація списку людей
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;
public class DeserializeListDemo {
public static void main(String[] args) throws Exception {
FileInputStream fileIn = new FileInputStream("people.bin");
ObjectInputStream in = new ObjectInputStream(fileIn);
// Відновлюємо список
List<Person> people = (List<Person>) in.readObject();
in.close();
fileIn.close();
for (Person p : people) {
System.out.println(p);
}
}
}
Результат:
Alice (30), secret: null
Bob (25), secret: null
5. Типові помилки
Помилка № 1: Клас не реалізує Serializable. Якщо забути додати implements Serializable, під час спроби серіалізації отримаєте NotSerializableException. Це найчастіша й найпростіша помилка.
Помилка № 2: Несеріалізовне поле. Якщо об’єкт містить поле, яке не є серіалізовним (наприклад, Thread, Socket або будь-який інший тип без Serializable), серіалізація «впаде». Позначте такі поля як transient або зробіть їх серіалізовними.
Помилка № 3: Зміна структури класу. Якщо об’єкт серіалізовано, а пізніше клас змінено (додали/видалили поля), під час читання можливий InvalidClassException. Вказуйте serialVersionUID для стабільного контролю версії.
Помилка № 4: Спроба серіалізувати статичні поля. Поля з модифікатором static не серіалізуються. Після десеріалізації їхні значення будуть такими, як у класі за замовчуванням, а не тими, що були під час серіалізації.
Помилка № 5: Потоки не закрито. Якщо не закривати потоки після роботи, можна отримати пошкоджений файл або витік ресурсів. Використовуйте try-with-resources або явно закривайте потоки.
Помилка № 6: Невідповідність класів. Якщо клас було перейменовано або переміщено в інший пакет, десеріалізація не спрацює — потрібна точна відповідність імені та пакета класу, збереженого в потоці.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ