EeBlog(テクニカルブログ)

第32回 直列化のカスタマイズ

今回のテーマは「直列化のカスタマイズ」です。

前回、直列化不可能な参照型のフィールドにtransientというフィールド修飾子をつけることで、そのフィールドが直列化の対象外となるという話をしました。 しかし、直列化不可能という理由でtransientをつけたフィールドに対しても、状態を保存しておきたいという場合があります。 このような場合に対応するために、今回は直列化の際に特別な働きをするメソッドを学びます。 具体的には、以下のシグネチャを持つメソッドです。

private void writeObject(java.io.ObjectOutputStream out) throws IOException;

private void readObject(java.io.ObjectInputStream in) throws

IOException,ClassNotFoundException;

これらのメソッドによって、直列化の際に特殊な扱いが必要なクラスの直列化処理をカスタマイズします。

では、サンプルで確認していきましょう。 Badmintonクラスには場所を表すplaceというフィールドを作りました。 今回、インターフェースは使いません。

public class Badminton { 
    private String place;
    public Badminton(String place) { 
        this.place = place; 
    }

    public void play() { 
        System.out.println(place + "バドミントンして遊びます。"); 
    }

    public String getPlace() { 
        return place; 
    } 
}

PlayerクラスにwriteObjectメソッドとreadObjectメソッドを用意しました。
writeObjectメソッドでは、最初にdefaultWriteObjectメソッドを呼び出し、デフォルトの直列化処理を行っています。
次に、badmintonそのものを保存はできない代わりに、badmintonのフィールドを呼び出して保存しています。
readObjectメソッドでは、writeObjectメソッドと同様に、最初にデフォルトの直列化復元処理を行った後、badmintonに直列化時と同じフィールドの値を持ったBadmintonオブジェクトを設定します。

import java.io.IOException; 
import java.io.ObjectInputStream; 
import java.io.ObjectOutputStream; 
import java.io.Serializable;

@SuppressWarnings("serial") 
public class Player implements Serializable { 
    private transient Badminton badminton;

    public Player(Badminton badminton) { 
        this.badminton = badminton; 
        System.out.println("コンストラクタです。"); 
    }

    public Badminton getBadminton() { 
        return badminton; 
    }

    public void play() { 
        System.out.println("ウォームアップします。");

        badminton.play();

        System.out.println("クールダウンします。"); 
    }

    private void writeObject(ObjectOutputStream oos) throws IOException { 
        oos.defaultWriteObject(); 
        oos.writeUTF(badminton.getPlace()); 
    }

    private void readObject(ObjectInputStream ois) throws IOException,
    ClassNotFoundException { 
        ois.defaultReadObject(); 
        badminton = new Badminton(ois.readUTF()); 
    } 
}

Mainクラスでは、deepCopyメソッドにApache Commonsを利用しています。
処理内容自体は前回とほとんど変わりません。

import java.io.Serializable;

import org.apache.commons.lang.SerializationUtils;

public class Main { 
    public static void main(String[] args) { 
        Player player = new Player(new Badminton("体育館で")); 
        player.play(); 
        System.out.println();

        Player clonePlayer = deepCopy(player); 
        clonePlayer.play(); 
        System.out.println();

        System.out.print("Playerのインスタンスが同じ:"); 
        System.out.println(player == clonePlayer); 
        System.out.print("Badmintonのインスタンスが同じ:"); 
        System.out.println(player.getBadminton() == clonePlayer.getBadminton()); 
    }

    @SuppressWarnings("unchecked") 
    public static <T extends Serializable> T deepCopy(T t) { 
        return (T) SerializationUtils.clone(t); 
    } 
}

Mainクラスを実行すると、transientが付いているフィールドの状態も復元されていることがわかると思います。

 

直列化の際に特別な働きをするメソッドは、writeObjectメソッドとreadObjectメソッドの他にもありますので、興味がある方はSerializableインターフェースのjavadocを参照してみてください。