JavaRush /Курси /JAVA 25 SELF /Серіалізація колекцій: List, Map, Set

Серіалізація колекцій: List, Map, Set

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

1. Серіалізація колекцій

Гарна новина: майже всі стандартні колекції Java (наприклад, ArrayList, HashSet, HashMap та їхні «друзі») вже реалізують Serializable. Це означає, що ви можете серіалізувати їх «з коробки», без складних танців з бубном.

Приклад:

import java.io.Serializable;
import java.util.ArrayList;

public class MyList extends ArrayList<String> implements Serializable {
    // Можна навіть нічого не писати — ArrayList вже Serializable!
}

На практиці ви зазвичай серіалізуєте стандартні колекції, і все працює без зайвого клопоту.

Як серіалізувати колекцію?

Для серіалізації колекції використовується той самий підхід, що й для будь-якого іншого об’єкта:

  • Створюємо колекцію.
  • Записуємо її у файл через ObjectOutputStream.
  • Читаємо назад через ObjectInputStream.

Приклад 1: Серіалізація ArrayList<String>

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

public class SerializeListDemo {
    public static void main(String[] args) throws Exception {
        // 1. Створюємо список рядків
        List<String> books = new ArrayList<>();
        books.add("Над прірвою у житі");
        books.add("Великі сподівання");
        books.add("Божественна комедія");

        // 2. Зберігаємо список у файл
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("books.ser"));
        out.writeObject(books);
        out.close();

        // 3. Читаємо список назад із файлу
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("books.ser"));
        List<String> loadedBooks = (List<String>) in.readObject();
        in.close();

        // 4. Перевіряємо результат
        System.out.println("Список книг після десеріалізації:");
        for (String book : loadedBooks) {
            System.out.println("- " + book);
        }
    }
}

Виведення:

Список книг після десеріалізації:
- Над прірвою у житі
- Великі сподівання
- Божественна комедія

Приклад 2: Серіалізація HashSet<Integer>

Set<Integer> numbers = new HashSet<>(Arrays.asList(10, 20, 30, 40));
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("numbers.ser"));
out.writeObject(numbers);
out.close();

ObjectInputStream in = new ObjectInputStream(new FileInputStream("numbers.ser"));
Set<Integer> loadedNumbers = (Set<Integer>) in.readObject();
in.close();

System.out.println("Множина після десеріалізації: " + loadedNumbers);

Приклад 3: Серіалізація HashMap<String, Integer>

Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 100);
scores.put("Bob", 80);

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("scores.ser"));
out.writeObject(scores);
out.close();

ObjectInputStream in = new ObjectInputStream(new FileInputStream("scores.ser"));
Map<String, Integer> loadedScores = (Map<String, Integer>) in.readObject();
in.close();

System.out.println("Мапа після десеріалізації: " + loadedScores);

Важливі моменти

Чи зберігається порядок?

  • Для List (наприклад, ArrayList) порядок елементів завжди зберігається.
  • Для Set (HashSet) порядок не гарантується (як до, так і після серіалізації).
  • Для Map (HashMap) порядок пар ключ-значення не гарантується (якщо потрібен порядок — використовуйте LinkedHashMap).

Порожні колекції серіалізуються та десеріалізуються без проблем. Після десеріалізації ви отримаєте порожній список/множину/мапу.

Вкладені колекції (наприклад, List<List<String>>) серіалізуються коректно, якщо всі вкладені елементи серіалізовні.

2. Вимоги до елементів колекції

Ось тут часто чатує перша серйозна пастка!

Усі елементи колекції також мають бути серіалізовними.

Якщо хоча б один елемент колекції не реалізує інтерфейс Serializable, серіалізація колекції завершиться помилкою NotSerializableException. До того ж, якщо колекція велика, а помилка — десь усередині, пошук винуватця перетворюється на захопливий квест.

Приклад: серіалізація колекції з несеріалізовним об’єктом

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

class NotSerializableClass {
    int value;
    public NotSerializableClass(int value) { 
        this.value = value; 
    }
}

public class NotSerializableDemo {
    public static void main(String[] args) throws Exception {
        List<Object> list = new ArrayList<>();
        list.add("Hello");
        list.add(new NotSerializableClass(123)); // <- Ой!

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("badlist.ser"));
        try {
            out.writeObject(list); // Тут буде викинуто виняток!
        } catch (NotSerializableException e) {
            System.out.println("Помилка серіалізації: " + e);
        }
        out.close();
    }
}

Результат:

Помилка серіалізації: java.io.NotSerializableException: NotSerializableClass

Що робити?

Впоратися з цим просто: усі елементи колекції мають бути або стандартними типами, такими, як String або Integer, або вашими власними класами, що реалізують інтерфейс Serializable. Якщо якийсь клас не серіалізовний, достатньо додати до нього implements Serializable, і проблема зникне.

3. Особливості серіалізації різних колекцій

Чи зберігається порядок елементів?

  • List: Так, порядок елементів зберігається.
  • Set: Залежить від реалізації. У HashSet порядок не гарантується, а от у LinkedHashSet — зберігається.
  • Map: У HashMap порядок не гарантується, у LinkedHashMap — зберігається порядок додавання.

Демонстрація:

List<String> list = Arrays.asList("A", "B", "C");
Set<String> set = new HashSet<>(list);

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("coll.ser"));
out.writeObject(list);
out.writeObject(set);
out.close();

ObjectInputStream in = new ObjectInputStream(new FileInputStream("coll.ser"));
List<String> loadedList = (List<String>) in.readObject();
Set<String> loadedSet = (Set<String>) in.readObject();
in.close();

System.out.println("List: " + loadedList); // завжди [A, B, C]
System.out.println("Set: " + loadedSet);   // може бути [A, C, B] тощо

Серіалізація порожніх колекцій

Порожні колекції серіалізуються без проблем. Після десеріалізації ви отримаєте порожній об’єкт потрібного типу.

Серіалізація вкладених колекцій

Можна серіалізувати колекції, які містять інші колекції:

List<List<String>> table = new ArrayList<>();
table.add(Arrays.asList("row1-col1", "row1-col2"));
table.add(Arrays.asList("row2-col1", "row2-col2"));

// Серіалізація
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("table.ser"));
out.writeObject(table);
out.close();

// Десеріалізація
ObjectInputStream in = new ObjectInputStream(new FileInputStream("table.ser"));
List<List<String>> loadedTable = (List<List<String>>) in.readObject();
in.close();

System.out.println(loadedTable);

4. Практичний приклад: серіалізація колекції об’єктів власного класу

Даваймо разом напишемо невеличкий застосунок для «віртуальної бібліотеки», де будемо серіалізувати список книжок. Клас Book має бути серіалізовним!

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

class Book implements Serializable {
    private static final long serialVersionUID = 1L; // для сумісності версій класу
    String title;
    String author;
    int year;

    public Book(String title, String author, int year) {
        this.title = title;
        this.author = author;
        this.year = year;
    }

    @Override
    public String toString() {
        return title + " (" + author + ", " + year + ")";
    }
}

public class LibraryApp {
    public static void main(String[] args) throws Exception {
        List<Book> books = new ArrayList<>();
        books.add(new Book("Над прірвою у житі", "Дж. Д. Селінджер", 1951));
        books.add(new Book("Великі сподівання", "Чарльз Діккенс", 1861));
        books.add(new Book("Божественна комедія", "Данте Аліг’єрі", 1320));

        // Зберігаємо список у файл
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("mylibrary.ser"));
        out.writeObject(books);
        out.close();

        // Читаємо список назад
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("mylibrary.ser"));
        List<Book> loadedBooks = (List<Book>) in.readObject();
        in.close();

        System.out.println("Книги з файлу:");
        for (Book b : loadedBooks) {
            System.out.println("- " + b);
        }
    }
}

5. Типові помилки під час серіалізації колекцій

Помилка № 1: NotSerializableException на елементі колекції. Якщо хоча б один елемент колекції несеріалізовний, серіалізація завершиться помилкою NotSerializableException. Наприклад, якщо ви випадково додали до колекції об’єкт, який не реалізує Serializable, або забули додати цей інтерфейс у свій клас.

Помилка № 2: Зміна класу між серіалізацією та десеріалізацією. Якщо ви серіалізували колекцію об’єктів, а потім змінили структуру класу (наприклад, додали або видалили поля), під час десеріалізації може виникнути виняток InvalidClassException. Щоб уникнути цього, використовуйте поле serialVersionUID у класі.

Помилка № 3: Серіалізація колекції з полями transient або static. Поля, позначені як transient або static, не серіалізуються. Якщо ваші об’єкти залежать від таких полів, після десеріалізації вони матимуть значення за замовчуванням (наприклад, null або 0).

Помилка № 4: Серіалізація колекцій із вкладеними несеріалізовними об’єктами. Якщо в колекції є вкладені колекції або об’єкти, що не реалізують Serializable, помилка виникне на найглибшому рівні — не завжди відразу зрозуміло, де шукати проблему. Перевіряйте всі рівні вкладеності!

Помилка № 5: Продуктивність на великих колекціях. Якщо колекція дуже велика, серіалізація може зайняти багато часу та місця на диску. У таких випадках варто замислитися про потокову серіалізацію або розбиття колекції на частини.

1
Опитування
Серіалізація, рівень 42, лекція 4
Недоступний
Серіалізація
Вступ до серіалізації об’єктів
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ