第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が発生します。
この問題を解決するため、次回は直列化処理における特別なメソッドを勉強します。