Javaシリアライゼーション:readObject()とreadResolve()の比較


127

Effective Java and other sources』では、シリアライズ可能なJavaクラスを操作するときにreadObject()メソッドをいつどのように使用するかについて、かなり説明しています。一方、readResolve()メソッドは、少し謎のままです。基本的に私が見つけたすべてのドキュメントは、2つのうちの1つだけに言及するか、両方に個別に言及します。

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

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

この件について少しお話しいただければ幸いです。


OracleのJDKの例:String.CaseInsensitiveComparator.readResolve()
kevinarpe

回答:


137

readResolveストリームから読み込まれたオブジェクトを置き換えるため使用されます。私がこれまでに見た唯一の用途は、シングルトンを強制することです。オブジェクトが読み込まれたら、それをシングルトンインスタンスに置き換えます。これにより、シングルトンをシリアル化および逆シリアル化することで、別のインスタンスを作成できないようになります。


3
悪意のあるコード(またはデータ)がそれを回避する方法はいくつかあります。
トム・ホーティン-2009

6
Josh Blochは、これが効果的なJava 2nd edで壊れる条件について話します。項目77.彼は数年前にGoogle IOで(講演の終わりに向けて)Google IOでこの講演でこれについて述べた:youtube.com/watch
v

17
transientフィールドについては触れられていないため、この答えはやや不十分です。読み込まれた後のオブジェクトの解決にreadResolve使用されます。使用例としては、オブジェクトが、既存のデータから再作成でき、シリアル化する必要のないキャッシュを保持している可能性があります。キャッシュされたデータを宣言することができるし、逆シリアル化後にそれを再構築することができます。そのようなことがこの方法の目的です。transientreadResolve()
Jason C

2
@JasonCの「[一時的な処理]のようなものがこのメソッドの目的である」というコメントは誤解を招くものです。Javaのドキュメントを参照してくださいSerializable:「ストリームからインスタンスが読み取られるときに置換を指定する必要があるクラスは、この[ readResolve]特別なメソッドを実装する必要があります...」と書かれています。
Opher、2016年

2
readResolveメソッドは、多数のオブジェクトをシリアル化してデータベースに格納したと想定される、まれなケースでも使用できます。後でそのデータを新しい形式に移行する場合は、readResolveメソッドで簡単に実現できます。
Nilesh Rajani

29

アイテム90、Effective Java、3rd EdがカバーreadResolvewriteReplace、シリアルプロキシ-主な用途。これらの例では、フィールドの読み取りと書き込みにデフォルトのシリアル化を使用しているためreadObjectwriteObjectメソッドとメソッドは書き出されません。

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

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


9

readResolveを使用して、readObjectメソッドを通じてシリアル化されるデータを変更できます。たとえば、xstream APIはこの機能を使用して、直列化復元されるXMLになかった一部の属性を初期化します。

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


1
XMLとXstreamはJavaシリアライゼーションに関する質問には関係がなく、質問は何年も前に正しく回答されました。-1
ローン侯爵2013

5
受け入れられた回答は、readResolveがオブジェクトを置き換えるために使用されることを示しています。この回答は、逆シリアル化中にオブジェクトを変更するために使用できる有用な追加情報を提供します。XStreamは、それが発生する唯一の可能なライブラリとしてではなく、例として提供されました。
2014

5

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


readResolve()は私には明白でしたが、それでも説明できない質問がいくつかありますが、あなたの答えは私の心を読んでください、ありがとう
Rajni Gangwar 2017年

5

readObject()はObjectInputStreamクラスの既存のメソッドです。逆シリアル化時にオブジェクトを読み取りながら、readObjectメソッドは、内部的に逆シリアル化されているクラスオブジェクトがreadResolveメソッドを持っているかどうかを確認します。インスタンス。

したがって、readResolveメソッドを作成する目的は、シリアライズ/デシリアライズによって別のインスタンスを取得できない純粋なシングルトン設計パターンを実現するための良い方法です。



2

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

readResolve()メソッドは静的メソッドではありません。in.readObject()逆シリアル化中にAfter が呼び出されると、返されたオブジェクトが以下のようにシリアル化されたオブジェクトと同じであることを確認します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 
}

1

私はこの質問が本当に古く、受け入れられた答えがあることを知っていますが、Google検索で非常に高く表示されるので、私が重要だと思う3つのケースをカバーする回答が提供されていないので、私は比較検討すると思いました-これらの主な用途メソッド。もちろん、すべてカスタムシリアライゼーションフォーマットが実際に必要であると想定しています。

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

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

    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。これはjava.util.List、より長いスライスを公開するリスト実装などのビューの場合ArrayListです。明らかに、バッキングリスト全体をシリアル化することは悪い考えであり、表示されたスライスからのみ要素を書き込む必要があります。しかし、なぜそれで停止し、逆シリアル化の後に無意味なレベルの間接参照があるのですか?ArrayListビュークラスでラップする代わりに、ストリームからに要素を読み込んで直接返すことができます。

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


0

readResolveメソッド

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

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

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

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


0

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

の例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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.