[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にします。

1. Serializableインターフェース実装クラスに定義したwriteObjectメソッド
アクセス修飾子 private
戻り値 void
メソッド名 writeObject
引数 ObjectOutputStream
例外 IOException

readObject

Serializableインターフェース実装クラスreadObjectは、シリアライズデータをデシリアライズするときにJVMから呼び出されるメソッドです。ObjectOutputStreamを引数に取り、ここにwriteObjectメソッドで任意のフィールドを書き出します。

JVMから呼び出されるメソッドなのでアクセス修飾子はprivateにします。

2. Serializableインターフェース実装クラスに定義したreadObjectメソッド
アクセス修飾子 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でシリアライズデータを読み込む順番は合わせないといけない。

コメント

このブログの人気の投稿

docker-compose up で proxyconnect tcp: dial tcp: lookup proxy.example.com: no such host

docker-compose で起動したweb、MySQLに接続できない事象

【PHP】PHP_CodeSnifferを使う(コーディングルールのカスタマイズ)