ホーム › シリアライズ・デシリアライズ › Ser-03
Ser-03: Externalizable によるカスタムシリアライズ
Externalizable はSerializableより細かいシリアライズ制御が可能なインターフェースです。writeExternal()とreadExternal()を自分で実装することで、保存するフィールドをバイト単位で完全にカスタマイズできます。
いつ使うか
- 保存するフィールドを細かく選択・制御したいとき(transient より厳密な管理が必要な場合)
- シリアライズのデータ形式を完全にカスタマイズしてサイズを最小化したいとき
- シリアライズ時に値の変換(暗号化・圧縮など)を挟みたいとき
Serializable vs Externalizable の比較
| 特性 | Serializable | Externalizable |
|---|---|---|
| 実装の手間 | 少ない(マーカーインターフェース) | 多い(writeExternal / readExternal を実装) |
| 保存フィールドの制御 | transient で除外のみ | 完全なカスタマイズ可能 |
| 引数なしコンストラクタ | 不要 | public 引数なしコンストラクタが必須 |
| record との併用 | 可能(implements Serializable) | 不可(final フィールドに代入できない) |
サンプルコード
Externalizable では writeExternal で書いたフィールドと readExternal で読む順序を完全に一致させる必要があります。順序がずれると別のフィールドの値が混入して壊れたオブジェクトが復元されます。また public 引数なしコンストラクタが必須なのは、デシリアライズ時に JVM が最初にそのコンストラクタでオブジェクトを生成してから readExternal を呼ぶためです。
よくあるミス・注意点
writeExternal と readExternal の順序は完全一致が必須
writeExternal で書いた順序とreadExternal で読む順序が一致しないと、 別のフィールドの値が混入した壊れたオブジェクトが生成されます。 例えば productId を書いた後に productName を書いたなら、 読み込みも必ず productId → productName の順にしてください。
public 引数なしコンストラクタがないと InstantiationException になる
Externalizable を実装したクラスのデシリアライズ時、 JVM はまず引数なしコンストラクタでオブジェクトを生成し、 その後に readExternal を呼び出します。 引数なしコンストラクタが存在しないか public でない場合は 例外が発生します。必ず public ProductCatalog() {} を定義してください。
record には Externalizable を実装できない
Java 17 以降の record は すべてのコンポーネントが final のため、readExternal で代入できません。 カスタムシリアライズが必要な場合は通常クラスを使用してください。 record を使いたい場合は、全コンポーネントをシリアライズするSerializable のみ実装できます。
テストする観点
- writeExternal で指定したフィールドが、デシリアライズ後に正しく復元されること
- writeExternal で指定しなかったフィールド(internalNote)が、デシリアライズ後に null になること
- public 引数なしコンストラクタを削除すると、デシリアライズ時に例外が発生すること
- writeExternal と readExternal の読み書き順序を逆にすると、フィールド値が混入した壊れたオブジェクトが生成されること(境界値テスト)
- シリアライズしたデータのバイトサイズが Serializable より小さいこと(不要フィールドを除外した効果の確認)