web-dev-qa-db-ja.com

Javaシリアライゼーション:readObject()vs. readResolve()

Effective Javaおよび他のソースは、シリアライズ可能Javaクラス。readResolve()基本的に、私が見つけたすべての文書は、2つのうちの1つだけに言及するか、両方に個別に言及するかのいずれかです。

未回答のままの質問は次のとおりです。

  • 2つの方法の違いは何ですか?
  • どの方法をいつ実装する必要がありますか?
  • ReadResolve()は、特に何を返すという点で、どのように使用する必要がありますか?

この問題に光を当てることができれば幸いです。

124
Forage

readResolveは、置換ストリームから読み取られたオブジェクトに使用されます。私がこれまで見てきた唯一の用途は、シングルトンを強制することです。オブジェクトが読み取られたら、シングルトンインスタンスに置き換えます。これにより、シングルトンをシリアライズおよびデシリアライズすることで、誰も別のインスタンスを作成できなくなります。

130
Michael Myers

readResolveメソッドは、ObjectInputStreamがストリームからオブジェクトを読み取り、呼び出し元に返す準備をしているときに呼び出されます。 ObjectInputStreamは、オブジェクトのクラスがreadResolveメソッドを定義しているかどうかを確認します。メソッドが定義されている場合、readResolveメソッドが呼び出され、ストリーム内のオブジェクトが返されるオブジェクトを指定できるようにします。返されるオブジェクトは、すべての用途と互換性のあるタイプである必要があります。互換性がない場合、タイプの不一致が検出されると、ClassCastExceptionがスローされます。

40
AZ_

項目90、Effective Java、第3版では、シリアルプロキシのreadResolveおよびwriteReplaceを取り上げています-主な用途。これらの例は、フィールドの読み取りと書き込みにデフォルトのシリアル化を使用しているため、readObjectメソッドとwriteObjectメソッドを書き出しません。

readResolvereadObjectが返された後に呼び出されます(逆にwriteReplacewriteObjectの前に呼び出され、おそらく別のオブジェクトで呼び出されます)。メソッドが返すオブジェクトは、ObjectInputStream.readObjectのユーザーに返されるthisオブジェクトと、ストリーム内のオブジェクトへのその他の後方参照を置き換えます。 readResolvewriteReplaceの両方が、同じタイプまたは異なるタイプのオブジェクトを返す場合があります。フィールドをfinalにする必要があり、下位互換性が必要な場合、または値をコピーおよび/または検証する必要がある場合には、同じ型を返すことが役立ちます。

readResolveを使用しても、シングルトンプロパティは強制されません。

28

readResolveを使用して、readObjectメソッドでシリアル化されたデータを変更できます。例えばxstream APIはこの機能を使用して、XMLに含まれていない一部の属性を初期化解除します。

http://x-stream.github.io/faq.html#Serialization

9
endless

readResolveは、既存のオブジェクトを返す必要がある場合に使用します。マージする必要がある重複入力をチェックしているため、または(たとえば、最終的に一貫性のある分散システムで)古いバージョンに気付かないうちに到着する可能性がある更新のためです。

5
Pr0methean

readObject()は、ObjectInputStream class。 readResolveメソッドを呼び出して、同じインスタンスを返します。

したがって、readResolveメソッドを書くのは、シリアライズ/デシリアライズによって誰も別のインスタンスを取得できない、純粋なシングルトンデザインパターンを実現するための優れた手法です。

3

readResolve()は、シリアル化中にシングルトンコントラクトを保証します。
お願いします 参照

3

オブジェクトをファイルに保存できるようにシリアル化を使用してオブジェクトを変換する場合、メソッドreadResolve()をトリガーできます。このメソッドはプライベートであり、逆シリアル化中にオブジェクトが取得される同じクラスに保持されます。逆シリアル化後、返されるオブジェクトがシリアル化されたものと同じになるようにします。つまり、instanceSer.hashCode() == instanceDeSer.hashCode()

readResolve()メソッドは静的メソッドではありません。逆シリアル化中にin.readObject()が呼び出された後、返されるオブジェクトがout.writeObject(instanceSer)の間にシリアル化されたものと同じであることを確認するだけです。

..
    ObjectOutput out = new ObjectOutputStream(new FileOutputStream("file1.ser"));
    out.writeObject(instanceSer);
    out.close();

この方法では、同じインスタンスが返されるたびにシングルトンデザインパターンの実装にも役立ちます。

public static ABCSingleton getInstance(){
    return ABCSingleton.instance; //instance is static 
}
2
hi.nitish

私はこの質問が本当に古く、受け入れられた答えを持っていることを知っていますが、Google検索で非常に高くポップアップするので、私が重要だと思う3つのケースをカバーする答えが提供されていないので、私は重く思った-これらの主な用途メソッド。もちろん、すべてが実際にカスタムシリアル化形式が必要であると仮定しています。

たとえば、コレクションクラスを取り上げます。リンクリストまたはBSTの既定のシリアル化では、要素を順番にシリアル化するのに比べて、パフォーマンスがほとんど向上せず、スペースが大幅に失われます。これは、コレクションが投影またはビューである場合にさらに当てはまります-パブリックAPIによって公開されるよりも大きな構造への参照を保持します。

  1. シリアル化されたオブジェクトにカスタムシリアル化が必要な不変のフィールドがある場合、逆シリアル化されたオブジェクトが作成されるため、_writeObject/readObject_の元のソリューションは不十分です。beforewriteObject。リンクリストのこの最小限の実装を取り上げます。

    _public class List<E> extends Serializable {
        public final E head;
        public final List<E> tail;
    
        public List(E head, List<E> tail) {
            if (head==null)
                throw new IllegalArgumentException("null as a list element");
            this.head = head;
            this.tail = tail;
        }
    
        //methods follow...
    }
    _

この構造は、各リンクのheadフィールドに再帰的にnull値を続けて書き込むことでシリアル化できます。ただし、そのような形式の逆シリアル化は不可能になります。readObjectはメンバーフィールドの値を変更できません(現在はnullに固定されています)。ここにwriteReplace/readResolveペアがあります:

_private Object writeReplace() {
    return new Serializable() {
        private transient List<E> contents = List.this;

        private void writeObject(ObjectOutputStream oos) {
            List<E> list = contents;
            while (list!=null) {
                oos.writeObject(list.head);
                list = list.tail;
            }
            oos.writeObject(null);
        }

        private void readObject(ObjectInputStream ois) {
            List<E> tail = null;
            E head = ois.readObject();
            if (head!=null) {
                readObject(ois); //read the tail and assign it to this.contents
                this.contents = new List<>(head, this.contents)
            }                     
        }


        private Object readResolve() {
            return this.contents;
        }
    }
}
_

上記の例がコンパイル(または動作)しない場合は申し訳ありませんが、うまくいけば私のポイントを説明するのに十分です。これが非常にフェッチされた例であると思われる場合は、多くの関数型言語がJVM上で実行されることを覚えておいてください。この場合、このアプローチは不可欠になります。

  1. ObjectOutputStreamに書き込んだものとは異なるクラスのオブジェクトを実際にデシリアライズしたい場合があります。これは、より長いArrayListからスライスを公開する_Java.util.List_リスト実装などのビューの場合です。明らかに、バッキングリスト全体をシリアル化することは悪い考えであり、表示されたスライスの要素のみを記述する必要があります。しかし、なぜそれで停止し、デシリアライズ後に無用なレベルの間接性を持っているのですか?ストリームから要素をArrayListに読み込んで、ビュークラスでラップする代わりに直接返すことができます。

  2. または、シリアル化専用の同様のデリゲートクラスを用意することもできます。良い例は、シリアル化コードを再利用することです。たとえば、ビルダークラス(StringのStringBuilderと同様)がある場合、空のビルダーをストリームに書き込み、コレクションのサイズとコレクションの反復子によって返される要素を書き込むことで、コレクションをシリアル化するシリアル化デリゲートを記述できます。逆シリアル化には、ビルダーの読み取り、その後に読み取られるすべての要素の追加、およびデリゲートreadResolveからの最終的なbuild()の結果の返送が含まれます。その場合、コレクション階層のルートクラスでのみシリアル化を実装する必要があり、抽象iterator()およびbuilder()を実装していれば、現在または将来の実装から追加のコードは必要ありません。メソッド(同じタイプのコレクションを再作成するための後者-これ自体は非常に便利な機能です)。別の例では、完全に制御できないコードのクラス階層があります-サードパーティライブラリの基本クラスには、何も知らないプライベートフィールドがいくつもあり、バージョンによって変更される可能性がありますシリアル化されたオブジェクト。その場合、デシリアライズ時にデータを書き込み、オブジェクトを手動で再構築する方が安全です。

0
Turin

既に回答したように、readResolveは、オブジェクトの逆シリアル化中にObjectInputStreamで使用されるプライベートメソッドです。これは、実際のインスタンスが返される直前に呼び出されます。シングルトンの場合、ここでは、逆シリアル化されたインスタンス参照の代わりに、既存のシングルトンインスタンス参照を強制的に返すことができます。同様に、ObjectOutputStreamにはwriteReplaceがあります。

readResolveの例:

import Java.io.FileInputStream;
import Java.io.FileNotFoundException;
import Java.io.FileOutputStream;
import Java.io.IOException;
import Java.io.ObjectInputStream;
import Java.io.ObjectOutputStream;
import Java.io.Serializable;

public class SingletonWithSerializable implements Serializable {
private static final long serialVersionUID = 1L;

public static final SingletonWithSerializable INSTANCE = new SingletonWithSerializable();

private SingletonWithSerializable() {
    if (INSTANCE != null)
        throw new RuntimeException("Singleton instance already exists!");
}

private Object readResolve() {
    return INSTANCE;
}

public void leaveTheBuilding() {
    System.out.println("SingletonWithPublicFinalField.leaveTheBuilding() called...");
}

public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
    SingletonWithSerializable instance = SingletonWithSerializable.INSTANCE;

    System.out.println("Before serialization: " + instance);

    try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("file1.ser"))) {
        out.writeObject(instance);
    }

    try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("file1.ser"))) {
        SingletonWithSerializable readObject = (SingletonWithSerializable) in.readObject();
        System.out.println("After deserialization: " + readObject);
    }

}

}

出力:

Before serialization: com.ej.item3.SingletonWithSerializable@7852e922
After deserialization: com.ej.item3.SingletonWithSerializable@7852e922
0
Omkar

readResolveメソッド

SerializableクラスとExternalizableクラスの場合、readResolveメソッドを使用すると、クラスは、呼び出し元に返される前にストリームから読み取られたオブジェクトを置換/解決できます。 readResolveメソッドを実装することにより、クラスは、逆シリアル化される独自のインスタンスの型とインスタンスを直接制御できます。メソッドは次のように定義されます。

ANY-ACCESS-MODIFIERオブジェクトreadResolve()はObjectStreamExceptionをスローします;

readResolveメソッドは、ObjectInputStreamがストリームからオブジェクトを読み取ったときに呼び出されますそして、それを呼び出し元に返す準備をしています。 ObjectInputStreamは、オブジェクトのクラスがreadResolveメソッドを定義しているかどうかを確認します。メソッドが定義されている場合、readResolveメソッドが呼び出されて、ストリーム内のオブジェクトが返されるオブジェクトを指定できるようにします。返されるオブジェクトは、すべての用途と互換性のあるタイプである必要があります。互換性がない場合、タイプの不一致が検出されると、ClassCastExceptionがスローされます。

たとえば、仮想マシン内に存在する各シンボルバインディングのインスタンスが1つだけであるSymbolクラスを作成できます。 readResolveメソッドを実装して、そのシンボルが既に定義されているかどうかを判断し、既存の同等のSymbolオブジェクトを置き換えてアイデンティティ制約を維持します。このようにして、Symbolオブジェクトの一意性をシリアル化全体で維持できます。

0
Ankush soni