ホーム › シリアライズ・デシリアライズ › Ser-01
Ser-01: バイナリシリアライズ
Java 標準のObjectOutputStream/ObjectInputStreamを使うと、オブジェクトをバイト列に変換してファイルに保存したり、ネットワーク経由で送受信したりできます。 この仕組みを「シリアライズ(直列化)」と呼びます。
いつ使うか
- オブジェクトの状態をファイルに保存して、後で復元したいとき
- ネットワーク越しにオブジェクトをそのまま送受信したいとき(RMI など)
- キャッシュデータをバイナリ形式で一時保存したいとき
シリアライズの仕組み
| 手順 | 内容 |
|---|---|
| 1. implements Serializable | 対象クラスに Serializable を実装(メソッドの実装は不要) |
| 2. ObjectOutputStream | writeObject() でオブジェクトをバイト列に変換して出力 |
| 3. ObjectInputStream | readObject() でバイト列をオブジェクトに復元 |
| 4. キャスト | readObject() の戻り値は Object 型なので、元の型にキャストする |
サンプルコード
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 がスローされること(境界値: シリアライズ後にクラスを変更)