[Java] シリアライズとデシリアライズ
入出力ストリームを利用してインスタンスをファイルに書き出すことをシリアライズ、ファイルから読み込むことをデシリアライズと言います。
Serializableインターフェース
Serializableインターフェースはシリアライズ、デシリアライズさせたいクラスに実装させるインターフェースです。
このインターフェースは、マーカーインターフェースといい、シリアライズするクラスであるということをJVMへ示すためだけのものです。メソッドは何も持っていません。
コード例
ここではidとnameというフィールドを保持するPersonクラスを定義し、Serializableインターフェースを実装しています。
import java.io.Serializable;
public class Person implements Serializable {
private int id;
private String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "id:" + id + ", name:" + name;
}
}
- Serializableインターフェースはマーカーインターフェースであり、JVMがシリアライズ可能と判別するためのもの。メソッドは持たない。
シリアライズ
ObjectOutputStreamクラス
オブジェクトをシリアライズしてファイルに書き出すにはObjectOutputStreamクラスを使用します。
ObjectOutputStreamクラスのインスタンスを取得
ObjectOutputStreamクラスのコンストラクタは、FileOutputStreamクラスのオブジェクトを引数に取ります。
FileOutputStream fstream = new FileOutputStream("output/test.ser");
ObjectOutputStream ostream = new ObjectOutputStream(fstream);
ObjectOutputStreamクラスの主なメソッド
void writeObject(Object) | オブジェクトをシリアライズしてファイルに書き出す。 |
---|
コード例
Serializableインターフェースを実装したPersonクラスのインスタンスを、シリアライズしてファイルに書き出す例が以下です。
Person person = new Person(1001, "Serialize Test");
FileOutputStream fstream = new FileOutputStream("output/person.ser");
ObjectOutputStream ostream = new ObjectOutputStream(fstream);
try (fstream; ostream) {
ostream.writeObject(person);
System.out.print("Serializable -> " + person);
}
実行結果1
Serialized -> id:1001, name:Serialize Test
↓output/person.serファイルが生成されます。.serという拡張子は、Serializeの頭文字を取ったもので、シリアライズファイルの拡張子として慣習的に使用されています。
$ ls -l output/person.ser -rw-r--r-- 1 swata 197609 81 Nov 23 06:17 output/person.ser
実行結果2
もしPersonクラスがSerializableインターフェースを実装していない場合、wirteObjectメソッドで例外[NotSerializableException]が発生します。
Exception in thread "main" java.io.NotSerializableException: Person at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1185) at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:349) at SerializableTest.main(SerializableTest.java:13)
デシリアライズ
ObjectInputStreamクラス
ファイルからシリアライズされたデータを読み込んでオブジェクトに変換するには、ObjectOutputStreamクラスを使用します。
ObjectInputStreamクラスのインスタンスを取得
ObjectInputStreamクラスのコンストラクタは、FileInputStreamクラスのオブジェクトを引数に取ります。
FileInputStream fstream = new FileInputStream("output/person.ser");
ObjectInputStream ostream = new ObjectInputStream(fstream);
ObjectOutputStreamクラスの主なメソッド
Object readObject() | シリアライズされたデータを読み込んでオブジェクトを取得する。 |
---|
コード例
ファイルからシリアライズされたデータを読み込み、Personクラスにキャストしてコンソールへ出力します。
FileInputStream fstream = new FileInputStream("output/person.ser");
ObjectInputStream ostream = new ObjectInputStream(fstream);
try (fstream; ostream) {
Person person = (Person) ostream.readObject();
System.out.println("Deserialized -> " + person);
}
実行結果1
Deserialized -> id:1001, name:Serialize Test
実行結果2
Serializableインターフェースが実装されていないクラスに変換しようとすると、readObjectで実行時例外[InvalidClassException]が発生します。
Exception in thread "main" java.io.InvalidClassException: Person; class invalid for deserialization at java.base/java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:159) at java.base/java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:875) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2207) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1692) at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:499) at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:457) at DeserializeTest.main(DeserializeTest.java:10)
カスタムシリアライズ
クラスのすべてのフィールドをシリアライズするのではなく、シリアライズするフィールドを限定して対象項目のみをシリアライズするをカスタムシリアライズと言います。
カスタムシリアライズはシリアライズ時、デシリアライズ時のどちらか、もしくは両方で可能です。カスタムシリアライズを行うにはSerializableインターフェースを実装したクラスに、次のメソッドを追加します。
writeObject
Serializableインターフェース実装クラスに定義したwriteObjectメソッドは、インスタンスがシリアライズ化するときにJVMから呼び出されるメソッドです。ObjectOutputStreamを引数に取り、ここにwriteObjectメソッドで任意のフィールドを書き出します。
JVMから呼び出されるメソッドなのでアクセス修飾子はprivateにします。
アクセス修飾子 | private |
---|---|
戻り値 | void |
メソッド名 | writeObject |
引数 | ObjectOutputStream |
例外 | IOException |
readObject
Serializableインターフェース実装クラスreadObjectは、シリアライズデータをデシリアライズするときにJVMから呼び出されるメソッドです。ObjectOutputStreamを引数に取り、ここにwriteObjectメソッドで任意のフィールドを書き出します。
JVMから呼び出されるメソッドなのでアクセス修飾子はprivateにします。
アクセス修飾子 | private |
---|---|
戻り値 | void |
メソッド名 | readObject |
引数 | ObjectInputStream |
例外 | IOException, ClassNotFoundException |
コード例
以下の例では、writeObjectでid属性を書き出していますが、name属性は設定値を無視してnullを書き出しています。
また、メソッドが呼ばれたことが分かるようにログを出力しています。
private void writeObject(ObjectOutputStream outputStream)
throws IOException {
System.out.println("writeObject");
outputStream.writeObject(id);
outputStream.writeObject(null);
}
private void readObject(ObjectInputStream inputStream)
throws IOException, ClassNotFoundException {
System.out.println("readObject");
id = (int) inputStream.readObject();
name = (String) inputStream.readObject();
}
実行結果
# シリアライズ $ java SerializableTest.java writeObject Serializable -> id:1001, name:Serialize Test # デシリアライズ $ java DeserializeTest.java readObject Deserialized -> id:1001, name:null
実行結果を確認すると、シリアライズ/デシリアライズ時にそれぞれwriteObjectメソッド/readObjectメソッドが呼び出されていることが分かります。
また、デシリアライズの結果name属性がnullになっているので、カスタムシリアライズされていることが分かります。
- カスタムシリアライズを行う場合、writeObjectとreadObjectをSerializableインターフェース実装クラスに定義する。
- writeObjectで書き出すフィールドの順番と、readObjectでシリアライズデータを読み込む順番は合わせないといけない。
コメント
コメントを投稿