java-recipes

ホーム シリアライズ・デシリアライズ › Ser-01

Ser-01: バイナリシリアライズ

Java 標準のObjectOutputStream/ObjectInputStreamを使うと、オブジェクトをバイト列に変換してファイルに保存したり、ネットワーク経由で送受信したりできます。 この仕組みを「シリアライズ(直列化)」と呼びます。

いつ使うか

  • オブジェクトの状態をファイルに保存して、後で復元したいとき
  • ネットワーク越しにオブジェクトをそのまま送受信したいとき(RMI など)
  • キャッシュデータをバイナリ形式で一時保存したいとき

シリアライズの仕組み

手順内容
1. implements Serializable対象クラスに Serializable を実装(メソッドの実装は不要)
2. ObjectOutputStreamwriteObject() でオブジェクトをバイト列に変換して出力
3. ObjectInputStreamreadObject() でバイト列をオブジェクトに復元
4. キャストreadObject() の戻り値は Object 型なので、元の型にキャストする

サンプルコード

SerializationBasicSample.java
import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class SerializationBasicSample {

    // シリアライズ対象クラス(Serializable を実装)
    static class Employee implements Serializable {
        private static final long serialVersionUID = 1L;

        private final String employeeId;
        private final String name;
        private final String department;
        private int salary;

        Employee(String employeeId, String name, String department, int salary) {
            this.employeeId = employeeId;
            this.name = name;
            this.department = department;
            this.salary = salary;
        }

        @Override
        public String toString() {
            return "Employee{id='" + employeeId + "', name='" + name
                    + "', dept='" + department + "', salary=" + salary + "}";
        }
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // シリアライズするオブジェクトを作成
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee("E001", "田中太郎", "開発部", 400000));
        employees.add(new Employee("E002", "鈴木花子", "営業部", 380000));

        String filePath = "employees.dat";

        // シリアライズ(オブジェクト → バイト列 → ファイル)
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(filePath))) {
            oos.writeObject(employees);
            System.out.println("シリアライズ完了: " + filePath);
        }

        // デシリアライズ(ファイル → バイト列 → オブジェクト)
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream(filePath))) {
            @SuppressWarnings("unchecked")
            List<Employee> loaded = (List<Employee>) ois.readObject();
            System.out.println("デシリアライズ完了:");
            for (Employee emp : loaded) {
                System.out.println("  " + emp);
            }
        }

        // ファイル削除(クリーンアップ)
        new File(filePath).delete();
    }
}

Serializable インターフェースはメソッドを持たないマーカーインターフェースです。実装するだけでオブジェクトをバイト列に変換できます。serialVersionUID を明示的に定義しておくと、クラス変更時の互換性管理が容易になります(詳細は Ser-02 で解説します)。

よくあるミス・注意点

Serializable を実装していないクラスは NotSerializableException になる

ObjectOutputStream.writeObject() を呼ぶと、 対象クラスが Serializable を実装していない場合はjava.io.NotSerializableException がスローされます。 フィールドに含まれるオブジェクトも全て Serializable である必要があります。

serialVersionUID を省略するとクラス変更で InvalidClassException になる

serialVersionUID を定義しないと、 コンパイラがクラスの構造から自動生成します。クラスにフィールドを追加・削除すると serialVersionUID が変わり、古いファイルを読み込むとInvalidClassException が発生します。private static final long serialVersionUID = 1L; と明示的に定義する習慣をつけましょう。

readObject() の戻り値は必ずキャストが必要

readObject()Object 型を返すため、 元の型にキャストする必要があります。List<Employee> のようなジェネリクス型へのキャストでは unchecked 警告が出ます。@SuppressWarnings("unchecked") を付けて抑制するか、 キャスト前に instanceof で型チェックする方法があります。

セキュリティリスク:信頼できないデータのデシリアライズは危険

インターネットや外部システムから受け取ったバイナリデータをデシリアライズすると、 悪意のあるクラスが実行される「デシリアライズ攻撃」の危険性があります。 信頼できないソースからのデータには Java のシリアライズを使わず、 JSON(Ser-01 の応用)など安全なフォーマットを検討しましょう。

テストする観点

  • シリアライズしてデシリアライズしたオブジェクトが、元のオブジェクトと同じ値を持つこと
  • 複数オブジェクトを List に入れてシリアライズ・デシリアライズしても、全件復元されること
  • Serializable を実装していないクラスを writeObject() すると NotSerializableException がスローされること
  • ファイルが存在しない場合に readObject() すると FileNotFoundException がスローされること
  • serialVersionUID が一致しないクラスで readObject() すると InvalidClassException がスローされること(境界値: シリアライズ後にクラスを変更)

GitHub でソースコードを見る →