EeBlog(テクニカルブログ)

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