深く考えずに implements Serializableしていたけど、ちょっと気になって調べてみた。
下記の参考記事が非常に参考になるのでこちらを読むだけで良いと思う。
他は自分用のメモ書き。
参考記事
直列化のポイント
Serializableインターフェース
Serializableは直列化可能ですよと示すだけの役割。
implementsしたからといって直列化可能になるわけではない。
なんとSerializableインターフェースの中身は空なようです。
直列化インタフェースはメソッドまたはフィールドがなく、直列化可能であるという意味を識別する機能だけを備えています。
Serializable (Java Platform SE 8)
直列化(Serialize)とは
インスタンスをbyte配列に変換すること。
直列化復元とは
byte配列をインスタンスに復元すること。
直列化すると何ができるの?
ファイルやDBに保存することができる。
ネットワークを介して送信も可能。
JavaBeanがSerializableなのはDBに保存するクラスだから。
serialVersionUIDは何なの?
バージョン管理用のID。
例えばこんな感じ。
対策としてserialVersionUIDを使う。
- SerializableなクラスAインスタンスを保存
- クラスAに変更を加える
- serialVersionUIDを変更する
- 前に保存したクラスAはバージョン違いで復元不能だとわかる
- 新しいバージョンでクラスAインスタンスを保存する
- 復元可能!
serialVersionUIDのもう1つの役割
serialVersionUIDを宣言しない場合は自動的に計算される。
ただし、この計算はコンパイラによって異なる場合がある。
保存時のコンパイラと復元時のコンパイラが異なると復元不能なことがあるようです。
直列化可能クラスがserialVersionUIDを明示的に宣言しない場合、直列化ランタイムは「Java(TM)オブジェクト直列化仕様」で説明されているように、クラスのさまざまな側面に基づいて、クラスのserialVersionUIDのデフォルト値を計算します。ただし、すべての直列化可能クラスがserialVersionUID値を明示的に宣言することを強くお薦めします。これは、デフォルトのserialVersionUIDの計算が、コンパイラの実装によって異なる可能性のあるクラスの詳細にきわめて影響を受けやすく、直列化復元中に予期しないInvalidClassExceptionが発生する可能性があるためです。したがって、javaコンパイラの実装が異なってもserialVersionUID値の一貫性を確保にするには、直列化可能クラスがserialVersionUID値を明示的に宣言しなければいけません。
Serializable (Java Platform SE 8)
なのでserialVersionUIDは明示的に宣言しておきましょう。
ちなみにstatic final long であれば値は何でも良いようで。
あれ?でもコンパイラが異なるっていうのはどういう時だろう。
Javaのバージョンが変わるとコンパイラも変わるのかな。
直列化をよく見るところ
DBに保存するクラス。JavaBeanとか。
StrutusのActionFormクラスもSerializable。
あとはSessionに渡すクラス。
APサーバーを停止する時に、一時的にSessionを外部に保存する。
この時に直列化が必要。
直列化・復元するクラス
ObjectOutputStreamクラスで直列化するよ。
ObjectInputStreamクラスで復元するよ。
サンプルコード
package tyokuretuka; import java.io.Serializable; /** * 直列化可能なクラス */ public class Wish implements Serializable{ private static final long serialVersionUID = 651987625L; private String[] wishList; public Wish(String[] wishList) { this.wishList = wishList; } public String[] getWishList() { return wishList; } }
package tyokuretuka; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class Serialization { /** * オブジェクトを直列化する * @param wish * @return File */ public File executeSerialise(Wish wish) { String filePath = "C:/Test/sample.txt"; File file = new File(filePath); FileOutputStream fos; ObjectOutputStream oos = null; try { fos = new FileOutputStream(file); oos = new ObjectOutputStream(fos); //テキストファイルにWishクラスインスタンスを保存 oos.writeObject(wish); oos.close(); } catch (IOException e) { e.printStackTrace(); } return file; } }
package tyokuretuka; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class Restoration { /** * オブジェクトの復元を実行する * @param file * @return Wish */ public Wish executeRestoration(File file) { FileInputStream fis = null; ObjectInputStream ois = null; Wish wish = null; try { //ファイルから復元 fis = new FileInputStream(file); ois = new ObjectInputStream(fis); //Wishクラスインスタンスを取り出す wish = (Wish) ois.readObject(); ois.close(); } catch(IOException | ClassNotFoundException e) { e.printStackTrace(); } return wish; } }
package tyokuretuka; import java.io.File; public class Main { public static void main(String[] args) { //Serializeクラスのインスタンス化 Wish wish = new Wish(new String[] {"5000兆円ほしい", "本当に!"}); //直列化&テキストファイルに保存 Serialization serialize = new Serialization(); File file = serialize.executeSerialise(wish); //復元 Restoration restoration = new Restoration(); wish = restoration.executeRestoration(file); for(String s : wish.getWishList()) System.out.println(s); } }
実行結果
5000兆円ほしい
本当に!
保存したファイルの中身
こんなんでした。
試しにファイルの中身変更してから復元するとこんなエラーが出ました。
java.io.StreamCorruptedException: invalid stream header: AC814520
at java.base/java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:963)
at java.base/java.io.ObjectInputStream.
オブジェクト・ストリームから読み込まれた制御情報が、内部整合性検査に違反していた場合にスローされます。