Serialize

Object Serialization とは

serialize (オブジェクト直列化)という機能を使って、オブジェクト群をストリームに変換して、ファイルに書き出してみましょう。

serialize というのは、オブジェクトなどをストリームに変換する操作のことで、図でいえばこんな感じです。marshaling と呼ばれることもあります。ストリームに変換するだけでは困るので、元に戻す deserialize/unmarshaling とペアになっています。

serialize.png

Java では、オブジェクト間の参照関係をキープしたまま、オブジェクトを簡単にストリームに変換できます。

プログラム例は、2個セットになっています。さきUseObjectOutputStream を実行して、オブジェクト群をファイル(sample.ser)に serialize し、次にUseObjectInputStream で元に戻してみます。実行順あるので気を付けて

出力側サンプルプログラム

(UseObjectOutputStream)

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filename));
ArrayList<String> obj = createSampleObj();
out.writeInt(2); /* 数字も保存可能。*/
out.writeObject(obj); /* 1回目 */
out.writeObject(new SampleSerializable(1.0, "name0", obj));/* 2回目 */
out.close();

主な利用クラス:

  • java.io.ObjectOutputStream
    • object をストリームに変換して出力することができる。上記の例では、出力対象は、FileOutputStream。ネットワーク通信でも利用可能。
    • writeObject(Object): 引数で指定されたオブジェクトを書き出す。指定されたオブジェクトから参照されたオブジェクトもまとめて出力を行う。一度出力したオブジェクトは、前回の値を再利用して使う(だから、サイクル状のオブジェクトも出力可能)。

今回保存したのは、順に

  • writeInt() で、“2” を保存。こういう基本データ型も保存可能。
  • writeObject() で、java.util.ArrayList<String>**: obj 保存
  • writeObject() で、上記配列を格納した自作オブジェクト保存(オブジェクト定義は、後述)

というわけで、続いて入力側/自作クラス定義に必要な処理を見ていきましょう。

入力側サンプルプログラム

UseObjectInputStream

ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename));
try {
    int num = in.readInt();
    System.out.println("num:" + num);
    Object obj1 = in.readObject();
    System.out.println("Obj1:" + obj1);
    SampleSerializable obj2 = (SampleSerializable) in.readObject();
    System.out.println("Obj2:" + obj2);
    System.out.println("obj1==obj2.elem: " + (obj1 == obj2.elem));
} finally {
    in.close();
}

主な利用クラス:

  • java.io.ObjectInputStream
    • ストリームから Serialize されたobject を復元することができる。この場合、入力ストリームは、FileInputStream。ネットワーク通信では、network stream から入力(受信)するためにも利用可能
    • readObject(): Object 読み込み用メソッド。これ以外にも各基本データ型用メソッドなどあり。

Serialize のポイントは、同じオブジェクトはきちんと同じオブジェクトと認識してくれることです。復元された場合も、グラフ構造が復元されています。デバッガなどで確認してみるとよいでしょう。

自作クラス(自作データ構造定義)側

SampleSerializable

public class SampleSerializable implements Serializable {
    private static final long serialVersionUID = -8502149233835734087L;
    double x;
    String name;
    List<String> elem;
    int p = 10;
    /* ... */
}

このクラスは、先ほど入出力されたデータ構造の一つです。基本的には普通のクラスと同じなんですが、Serialize できるように以下の特徴を持っています。

  • java.io.Serializable を implements している
    • 推奨されるので、serialVersionUID も定義してある

ちなみに、このクラスから参照されるクラスも Serializable でなくてはいけません。

  • 上の例の場合、String とか List<String> とか

といっても、Collections 系のクラスなどは Serializable ですし、自作クラスにだけ気をつければ問題ありません。

注意事項

  • 自作クラスを Serializable にした場合、クラスの中身を変更した後は、構造が変わるので、前に保存したデータは使えません。当然ですが。

    • serialVersionUID は、そういった「同じクラスでもバージョンが変わった」ってことを識別するために使われるものです。
  • writeObject(Object): は、一度出力したオブジェクトは、前回の値を再利用して使う。このため、プログラム実行中に、何度が同じオブジェクトの状態を変更しては出力するような場合、2回目以降の保存内容が最新の値と異なってしまう。そういうときは、reset() を呼んで、、送ったオブジェクトを忘れてもらいましょう。

あとは、参考程度の情報です。

  • 自作クラスを Serializable にする際、保存対象にしたくないフィールドがあれば、“transient” keyword をつけましょう。