第31回 transient
今回のテーマは「transient」です。
まず「transientとは何ぞや」というところから始めましょう。 transientはJavaの予約語の1つで、フィールド修飾子に分類されます。 transientを付けられたフィールドは、直列化の対象外となります。 では、どのような場合にtransientを使うのでしょうか。
transientを使う場合、2パターン考えられます。 1つは状態を保存する必要のない場合です。 直列化復元の際に初期化したいフィールドが該当します。 もう1つは直列化したいクラスに直列化不可能な参照型のフィールドがある場合です。
後者のケースを前回と似たサンプルで確認していきましょう。 前回、SportsインターフェースはSerializableインターフェースを継承しましたが、今回はしていません。
public interface Sports {
void play();
}
public class Badminton implements Sports {
@Override
public void play() {
System.out.println("バドミントンして遊びます。");
}
}
Playerクラスは前回と同じようにSerializableインターフェースをインプリメントしています。
import java.io.Serializable;
@SuppressWarnings("serial")
public class Player implements Serializable {
private Sports sports;
public Player(Sports sports) {
this.sports = sports;
System.out.println("コンストラクタです。");
}
public Sports getSports() {
return sports;
}
public void play() {
System.out.println("ウォームアップします。");
sports.play();
System.out.println("クールダウンします。");
}
}
Mainクラスも前回と同じです。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Main {
public static void main(String[] args) throws Exception {
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.getSports() == clonePlayer.getSports());
}
@SuppressWarnings("unchecked")
public static <T extends Serializable> T deepCopy(T t) throws Exception {
if (t == null) {
return null;
}
ByteArrayOutputStream byteOut = null;
ObjectOutputStream objectOut = null;
try {
byteOut = new ByteArrayOutputStream();
objectOut = new ObjectOutputStream(byteOut);
objectOut.writeObject(t);
} finally {
objectOut.close();
}
ObjectInputStream objectin = null;
T copy = null;
try {
objectin = new ObjectInputStream(new ByteArrayInputStream
(byteOut.toByteArray()));
copy = (T) objectin.readObject();
} finally {
objectin.close();
}
return copy;
}
}
このプログラムを実行するとjava.io.NotSerializableExceptionが発生します。
PlayerクラスのsportsというフィールドのBadmintonオブジェクトが直列化できないためですね。
このような状況でもPlayerクラスの直列化を行いたいときにtransientを用いるのです。
sportsフィールドに対してtransientを付け加えてみましょう。
import java.io.Serializable;
@SuppressWarnings("serial")
public class Player implements Serializable {
private transient Sports sports;
public Player(Sports sports) {
this.sports = sports;
System.out.println("コンストラクタです。");
}
public Sports getSports() {
return sports;
}
public void play() {
System.out.println("ウォームアップします。");
sports.play();
System.out.println("クールダウンします。");
}
}
Mainクラスを実行するとNotSerializableExceptionは発生しません。
transientにより、sportsフィールドのBadmintonオブジェクトが直列化の対象外になったからですね。
しかし、そのために直列化復元したPlayerのplayメソッドを使うとNullPointerExceptionが発生します。
この問題を解決するため、次回は直列化処理における特別なメソッドを勉強します。

