JavaRush /Курси /JAVA 25 SELF /ObjectOutputStream, ObjectInputStream: робота з потоками

ObjectOutputStream, ObjectInputStream: робота з потоками

JAVA 25 SELF
Рівень 42 , Лекція 3
Відкрита

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: Невідповідність класів. Якщо клас було перейменовано або переміщено в інший пакет, десеріалізація не спрацює — потрібна точна відповідність імені та пакета класу, збереженого в потоці.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ