CodeGym /コース /JAVA 25 SELF /コレクションのシリアライズ: List, Map, Set

コレクションのシリアライズ: List, Map, Set

JAVA 25 SELF
レベル 42 , レッスン 4
使用可能

1. コレクションのシリアライズ

良い知らせです: ほとんどの標準的な Java コレクション(たとえば ArrayListHashSetHashMap など)は、すでに 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)では、要素の順序は常に保持されます。
  • SetHashSet)では、順序は保証されません(シリアライズ前後とも)。
  • MapHashMap)では、キーと値の順序は保証されません(順序が必要なら 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

どう対処する?

対処は単純です: コレクション要素は StringInteger のような標準型であるか、またはインターフェース 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("ライ麦畑でつかまえて", "J. D. サリンジャー", 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 フィールドを持つコレクションのシリアライズ。 transientstatic とマークされたフィールドはシリアライズされません。これらのフィールドに依存しているオブジェクトは、デシリアライズ後に既定値(たとえば null0)になります。

エラー4: ネストの深いところに非シリアライズ可能なオブジェクトが含まれているコレクションのシリアライズ。 コレクション内に Serializable を実装していない入れ子のコレクションやオブジェクトがあると、最も深いレベルでエラーが発生します — 原因の箇所がすぐに分からないこともあります。すべてのネスト階層を確認しましょう!

エラー5: 大規模コレクションでのパフォーマンス。 コレクションが非常に大きい場合、シリアライズに時間やディスク容量が多く必要になることがあります。そうした場合は、ストリーム処理によるシリアライズやコレクションの分割を検討しましょう。

1
アンケート/クイズ
シリアライズ、レベル 42、レッスン 4
使用不可
シリアライズ
オブジェクトのシリアライズ入門
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION