web-dev-qa-db-ja.com

デフォルトのコンストラクタが指定されていない場合、Javaシリアライゼーションはどのようにして最終フィールドをデシリアライズしますか?

シリアル化する必要がある不変の値型を定義するクラスがあります。不変性は、コンストラクターで設定される最終フィールドから生じます。私はシリアル化を試してみましたが、(驚くべきことに)機能しますが、その方法がわかりません。

これがクラスの例です

public class MyValueType implements Serializable
{
    private final int value;

    private transient int derivedValue;

    public MyValueType(int value)
    {
        this.value = value;
        this.derivedValue = derivedValue(value);
    }

    // getters etc...
}

クラスに引数なしのコンストラクタがない場合、どのようにインスタンス化して最終的なフィールドを設定できますか?

(余談ですが、IDEAがこのクラスの「serialVersionUIDなし」の検査警告を生成していなかったため、このクラスに特に気付きましたが、シリアライズ可能にしたばかりの他のクラスの警告を正常に生成しました。 )

49
mdma

逆シリアル化は、JVMによって基本的な言語構成よりも低いレベルで実装されます。具体的には、コンストラクターを呼び出しません。

40

クラスに引数なしのコンストラクタがない場合、どのようにインスタンス化して最終的なフィールドを設定できますか?

いくつかの厄介な黒魔術が発生します。 JVMには、コンストラクターを呼び出さずにオブジェクトを作成できるバックドアがあります。新しいオブジェクトのフィールドは最初にデフォルト値(false、0、nullなど)に初期化され、次にオブジェクトの逆シリアル化コードがフィールドにオブジェクトストリームからの値を入力します。

(Javaはオープンソースなので、これを行うコードを読むことができます...そして泣きます!)

20
Stephen C

MichaelとStephenの両方が素晴らしい答えを出しました。transientフィールドについて注意するだけです。

逆シリアル化後にデフォルト値(参照の場合はnull、プリミティブの場合は0)が受け入れられない場合は、readObjectのバージョンを提供して初期化する必要があります。

    private void readObject (
            final ObjectInputStream s
        ) throws
            ClassNotFoundException,
            IOException
    {
        s.defaultReadObject( );

        // derivedValue is still 0
        this.derivedValue = derivedValue( value );
    }
12