EeBlog(テクニカルブログ)

第33回 直列化と継承

新年あけましておめでとうございます。 今年もJavaワンポイントをよろしくお願いします。

今回のテーマは「直列化と継承」です。

Javaの継承では、サブクラスのインスタンスを作成する際に、まずスーパークラスのコンストラクタが呼ばれます。 では、サブクラスが直列化可能かつスーパークラスが直列化不可能な状況で直列化をしたら、どうなるのでしょうか。

実際の動きを、サンプルで確認していきましょう。 今回、AbstractSportsという抽象クラスにplaceフィールドを作りました。 抽象クラスに対してnew演算子は使えないので、抽象クラスにコンストラクタがあるのを奇妙に思うかもしれませんが、サブクラスを生成するために必要なのです。

public abstract class AbstractSports { 
    private String place;

    public AbstractSports(String place) { 
        this.place = place; 
        System.out.println("AbstractSportsのコンストラクタです"); 
    }

    public abstract void play();

    public String getPlace() { 
        return place; 
    } 
}

BadmintonクラスはAbstractSportsを継承し、Serializableインターフェースをインプリメントしています。

import java.io.Serializable;

@SuppressWarnings("serial") 
public class Badminton extends AbstractSports implements Serializable { 
    public Badminton(String place) { 
        super(place); 
        System.out.println("Badmintonのコンストラクタです"); 
    }

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

PlayerクラスはBadminton型のフィールドを持ち、Serializableインターフェースをインプリメントしています。

import java.io.Serializable;

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

    public Player(Badminton badminton) { 
        this.badminton = badminton; 
    }

    public Badminton getBadminton() { 
        return badminton; 
    }

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

        badminton.play();

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

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クラスを実行すると、java.io.InvalidClassExceptionが発生します。 この例外は、クラスにアクセス可能な引数なしのコンストラクタがないことを、直列化ランタイムが検出した場合に投げられます。

 

実は直列化不可能なスーパークラスの場合、直列化復元時に引数なしのコンストラクタが呼ばれるのです。 AbstractSportsで引数なしのコンストラクタが定義されていないために、InvalidClassExceptionが発生したのですね。

では、本当に引数なしのコンストラクタが呼ばれるのかAbstractSportsを書き換えて確認してみましょう。

public abstract class AbstractSports { 
    private String place;

    public AbstractSports(String place) { 
        this.place = place; 
        System.out.println("AbstractSportsの引数有りのコンストラクタです"); 
    }

    public AbstractSports() { 
        this("運動場で"); 
        System.out.println("AbstractSportsの引数無しのコンストラクタです"); 
    }

    public abstract void play();

    public String getPlace() { 
        return place; 
    } 
}

直列化復元時にAbstractSportsの引数なしのコンストラクタが呼ばれたことが確認できたでしょうか。

 

直列化を行った場合、正常に復元されるフィールドと復元されないフィールドを判断できるようになっておきましょう。