4.オブジェクトシリアライゼーション

オブジェクトの状態を保存するための方法です。

(1) オブジェクトシリアライゼーションで状態を保存しよう

 前回までで、四角を表示する Bean を作成しました。イントロスぺクションの機能によって、BeanBox から四角の大きさ、色を変えることも可能にしました。しかし、いったん BeanBox を終了してしまうと、この変更は無効になってしまいます。今回は、この変更を保存する方法を紹介します。

Java プログラムで、オブジェクトの状態を保存するにはオブジェクトシリアライゼーションという方法を使います。これは、オブジェクトの状態、つまりクラス変数のデータを保存(シリアライズ)します。四角を表示する Bean で言えば、大きさと色を表す変数の値です。


 オブジェクトシリアライゼーションでは、オブジェクトの状態を保存するのであって、クラス定義をまるごと保存するわけではないことに注意してください。

 オブジェクトシリアライゼーションによってオブジェクトの状態を保存するには1つだけ条件があります。それは、 java.io.Serializable インタフェースを実装するということです。

import java.awt.*;
import java.io.Serializable;

public class RectangleBean extends Canvas implements Serializable {

 このソースは前回の四角を表示するBeanの最初の部分ですが、クラス定義をするときに Serializable インタフェースを実装しますよ、と宣言していることがわかります。

 「インタフェースを実装する場合、そのインタフェース内のメソッドをオーバーライドしなければいけないのでは?」と考えてしまいますが、この Serializable というインタフェースには、メソッドが定義されていません。このインタフェースは「シリアライズ可能ですよ」という目印のようにして使います。

オブジェクトシリアライゼーションはオブジェクト直列化、永続化とも言うよ。シリアライズ可能なオブジェクトは永続性があるとも言うよ。



(2) 保存できないものもある

 Serializable インタフェースを実装したクラスはシリアライズ可能になりますが、必ずしもすべてが保存されるわけではありません。

 ここまでは変数の値が保存されるとしましたが、実際には次の表のようになります。

シリアライズ可能 シリアライズ不可能
・ プリミティブ型(基本データ型)の値
・ 配列
・ 他のオブジェクトへの参照
 (この場合は、そのオブジェクトのデータと
  クラス名のみが保存される)
・ InputStream、OutputStream オブジェクト
・ Thread オブジェクト
                    など

 シリアライズ不可能なものは、復元したとき、あるいは復元先で、その値が正しく復元できないようなオブジェクトです。シリアライズ不可能な変数を含んだクラスをシリアライズするには、変数の宣言時に transient というキーワードを付けておきます。

import java.awt.*;
import java.io.Serializable;

public class RectangleBean extends Canvas implements Serializable {
    private int rectSize;
    private Color rectColor;
    private transient Thread kick; 

 transient キーワードを付けずに保存しようとすると、NoSerializableException になります。

アクセスレベル(public,private,protected,無指定)とシリアライゼーションは関係ありません。また、static 変数は保存されません。


(3) 保存のしかた

 実際にこの値を保存するためには、java.io.ObjectOutputStream クラスを使います。このクラス名からもわかるように、ObjectOutputStream クラスは OutputStream のサブクラスなので、使い方は他の OutputStream クラスと全く同じです。Stream については中級編15章からを参照してください。

 オブジェクトを保存するためのメソッドは OutjectOutputStream クラスの
void writeObject(Object obj) throws IOException

 というメソッドです。

 たとえば、オブジェクトの状態を rect.ser というファイルに保存するには次のように書きます。

public void save() {
    try {
        FileOutputStream fos = new FileOutputStream("rect.ser");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(this);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
    }
}

   ※ 厳密な例外処理はしていません。

シリアライズしたファイルは拡張子 ser がよく使われるよ。



(4) 復元のしかた

 ObjectOutputStream を使って保存したオブジェクトは java.io.ObjectInputStream クラスを使って復元します。このとき注意することは、はじめにも紹介したようにオブジェクトシリアライゼーションではクラス定義までは保存されないということです。

 たとえば、Aというコンピュータでシリアライズしたオブジェクトを、Bというコンピュータで復元しようとした場合、コンピュータBにもそのクラス定義がなければ復元できません。この場合、復元時に ClassNotFoundException になります。

 オブジェクトを復元するためのメソッドは OutjectInputStream クラスの

Object readObject() throws OptionalDataException,
ClassNotFoundException,
IOException

 というメソッドです。

 たとえば、オブジェクトの状態を rect.ser というファイルから復元するには次のように書きます。

public void load() {
    try {
        FileInputStream fis = new FileInputStream("rect.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        RectangleBean2 r = (RectangleBean2)ois.readObject();
        ois.close();
        fis.close();
        setRectSize( r.getRectSize() );
        setRectColor( r.getRectColor() );
        repaint();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

   ※ 厳密な例外処理はしていません。

 readObject( ) の戻り値は、すべてのオブジェクトのスーパークラスである Object クラスのオブジェクトなので、復元したオブジェクトを使うには、もとのクラスにキャストする必要があります。

もとのクラスにキャストするときに、きちんと検査をしたい場合は instanceof 演算子を使います(入門編18章参照)。

(5) BeanBox で試してみよう

 次のプログラムは、四角の状態を保存できるようにしたプログラムです。保存するメソッドが save( )、復元するメソッドが load( ) です。

  RectangleBean2.java

import java.awt.*;
import java.io.*;

public class RectangleBean2 extends Canvas implements Serializable {
    private int rectSize;
    private Color rectColor;
   
    public RectangleBean2() {
        setSize(50,50);
        rectSize = getWidth();
        rectColor = Color.blue;
    }
    
    public int getRectSize() {
        return rectSize;
    }
    
    public void setRectSize(int s) {
        rectSize = s;
    }
    
    public Color getRectColor() {
        return rectColor;
    }
    
    public void setRectColor(Color c) {
        rectColor = c;
    }

    public void paint(Graphics g) {
        g.setColor(rectColor);
        g.fillRect(0,0,rectSize,rectSize);
    }
    
    public void save() {
        try {
            FileOutputStream fos = new FileOutputStream("rect.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(this);
            oos.close();
            fos.close();
            System.out.println("シリアライズしました。");
        } catch (Exception e) {
            e.printStackTrace();
			System.out.println(e);
        }
    }
    
    public void load() {
        try {
            FileInputStream fis = new FileInputStream("rect.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            RectangleBean2 r = (RectangleBean2)ois.readObject();
            ois.close();
            fis.close();
            setRectSize( r.getRectSize() );
            setRectColor( r.getRectColor() );
            repaint();
            System.out.println("復元しました。");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


  コンパイル例

  MyManifest2.mf

Name:RectangleBean2.class
Java-Bean:True


  コンパイル

C:\ja...>jar cvfm RectangleBean2.jar MyManifest2.mf RectangleBean2.class 
added manifest
adding: RectangleBean2.class (in=2074) (out=1118) (deflated 46%)

 ここで作成した RectangleBean2.jar を <BDK_HOME>/jars にコピーして、BeanBox を起動してください。

 (1) RentangleBean2 をBeanBox に貼り付けます。このあと、右下をドラッグしてコンポーネントの表示領域を広げておきます。



 (2) 次に、ExplicitButton を1つ貼り付けて、このボタンを押したとき RectangleBean2 の save( ) を呼び出すようにします(このやりかたは応用編2章を参照してください)。

 (3) もう1つ ExplicitButton を貼り付けて、load( ) メソッドを呼び出すようにしてください。下の画面例では、ボタンを2つ貼り付けたあと、ラベルも変更しました。



 (4) ここで、save ボタンを押して RectangleBean2 をシリアライズします。MS-DOS プロンプトに 「シリアライズしました」と表示されるのを確認します。( rect.ser は <BDH_HOME>/beanbox にできます)

 (5) Properties ウインドウの rectColor、rectSize をクリックして、四角の大きさ、色を適当に変えます。



 (6) 最後に load ボタンを押すと、(4) で保存した青い四角に戻ります。

前の章(3.JARファイル)