writeObjectとreadObjectとは?カスタマイズ可能なシーケンス化プロセス(回転)

14638 ワード

(オブジェクトシーケンス化と逆シーケンス化、transientキーワード)
この文章はストレートで分かりやすい.翻訳してみると、原文はWhat are writeObject and readObject?Customizing the serialization process.
JavaでSerializationを使うのはかなり簡単です.シーケンス化したいオブジェクトがある場合は、Serializableインタフェースを実装するだけです.次に、ObjectOutputStreamを使用してオブジェクトをファイルに保存したり、他のホストに送信したりすることができます.すべてのnon-transientフィールドとnon-staticフィールドはシーケンス化され、逆シーケンス化再構成によって同じオブジェクト連絡図(例えば、多くの参照がオブジェクトを指している)が作成されます.しかし、自分のオブジェクトのシーケンス化と逆シーケンス化を実現したい場合があります.特定の状況でより多くの制御を得ることができます次の簡単な例を見てみましょう. 
import java.io.Serializable;

public class SessionDTO implements Serializable {  
    private static final long serialVersionUID = 1L;  
    private int data; // Stores session data  
  
    // Session activation time (creation, deserialization)  
    private long activationTime;   
  
    public SessionDTO(int data) {  
        this.data = data;  
        this.activationTime = System.currentTimeMillis();  
    }  
  
    public int getData() {  
        return data;  
    }  
  
    public long getActivationTime() {  
        return activationTime;  
    }  
}

以下に、上記のclassからファイルへのシーケンス化とその逆シーケンス化の主関数を示します. 
public class SerializeTester implements Serializable {  
    public static void main(String... strings) throws Exception {  
        File file = new File("out.ser");  
  
        ObjectOutputStream oos = new ObjectOutputStream(  
            new FileOutputStream(file));  
        SessionDTO dto = new SessionDTO(1);  
        oos.writeObject(dto);  
        oos.close();  
  
        ObjectInputStream ois = new ObjectInputStream(  
            new FileInputStream(file));  
        SessionDTO dto = (SessionDTO) ois.readObject();  
  
        System.out.println("data : " + dto.getData()  
            + " activation time : " + dto.getActivationTime());  
        ois.close();  
    }  
}

クラスSessionDTOは、2つのサーバ間で転送するセッションを示します.フィールドdataには、シーケンス化する必要がある情報が含まれています.しかし、セッションオブジェクトが任意のサーバに最初に表示される時間である別のフィールドactivationTimeがあります.私たちが伝えたい情報の列にはありません.このフィールドは、逆シーケンス化後に値を割り当てる必要があります.さらに、streamに置いてサーバに渡す必要はありません.不要な空間を占めているからです. これを解決するには、writeObjectとreadObjectを使用します.多くのJava書籍で無視され、多くの流行Java試験の一部ではないため、聞いたことがない人もいるかもしれません.これらの方法でSessionDTOを書き換えましょう. 
class SessionDTO implements Serializable {  
    private static final long serialVersionUID = 1L;  
    private transient int data; // Stores session data  
  
    //Session activation time (creation, deserialization)  
    private transient long activationTime;   
  
    public SessionDTO(int data) {  
        this.data = data;  
        this.activationTime = System.currentTimeMillis();  
    }  
  
    private void writeObject(ObjectOutputStream oos) throws IOException {  
        oos.defaultWriteObject();  
        oos.writeInt(data);  
        System.out.println("session serialized");  
    }  
  
    private void readObject(ObjectInputStream ois) throws IOException,  
            ClassNotFoundException {  
        ois.defaultReadObject();  
        data = ois.readInt();  
        activationTime = System.currentTimeMillis();  
        System.out.println("session deserialized");  
    }  
  
    public int getData() {  
        return data;  
    }  
  
    public long getActivationTime() {  
        return activationTime;  
    }  
}

メソッドwriteObjectはオブジェクトのシーケンス化を処理します.このメソッドを宣言すると、デフォルトのシーケンス化プロセスではなく、ObjectOutputStreamによって呼び出されます.初めて見た場合は、外部クラスに呼び出されているにもかかわらず、実際には2つのprivateの方法であることに驚くでしょう.java.lang.ObjectにもSerializableにも宣言されていません.では、ObjectOutputStreamはどのようにそれらを使用しますか?これか、ObjectOutputStreamは反射を使ってこの2つの方法を宣言したかどうかを探しています.ObjectOutputStreamはgetPrivateMethodを使用するため、これらのメソッドは、ObjectOutputStreamで使用するためにpriateとして宣言されなければなりません. 2つのメソッドの最初に、defaultWriteObject()とdefaultReadObject()が呼び出されていることがわかります.デフォルトのシーケンス化プロセスです.non-transientフィールドとnon-staticフィールドをすべて書き込み/読み取ります(ただし、serialVersionUIDのチェックは行いません).通常、私たちが処理したいフィールドはtransientとして宣言する必要があります.これにより、defaultWriteObject/defaultReadObjectは残りのフィールドに集中できますが、これらの特定のフィールド(transient)のシーケンス化をカスタマイズできます.その2つのデフォルトの方法を使用することは、強制的ではなく、複雑なアプリケーションを処理する際の柔軟性を与えます. 
ここから、シーケンス化に関する知識をもっと読むことができます. 
自分でもっと補足します. 1.Writeの順序とreadの順序は対応する必要があり、例えば複数のフィールドがwirteIntでストリームに1つずつ書き込まれている場合、readIntは順序に従って値を割り当てる必要がある. 2.Externalizable.このインタフェースはSerializableに継承されているため、シーケンス化を実現するには2つの方法がある.違いは、ExternalizableがreadExternalとwriteExternalの2つのメソッドを宣言していることであり、サブクラスは両方を実装する必要があります.Serializableは組み込みサポート、すなわち直接implementでよいが、Externalizableの実装クラスはreadExternalおよびwriteExternal実装を提供する必要がある.Serializableの場合、Javaは自分でオブジェクト図とフィールドを作成してオブジェクトをシーケンス化し、より多くのスペースを消費する可能性があります.Externalizableでは、プログラマが自分で書く/読む方法を制御する必要があります.面倒ですが、シーケンス化されたストレージの内容を効果的に制御することができます. 3.Effectvie Javaで述べたように、シーケンス化は別の構造関数のようにstreamによって作成されたにすぎない.フィールドにいくつかの条件が制限されている場合、特に非可変クラスが可変フィールドを定義している場合は、逆シーケンス化が問題になる可能性があります.readObjectメソッドに条件制限を追加することも、readResolveで行うこともできます.56の「保護されたreadObjectの作成」と「readResolveメソッドの提供」を参照してください. 4.deep cloneを提供する必要がある非常に複雑なオブジェクトがある場合、シーケンス可能として宣言することも考えられるが、欠点は明らかであり、パフォーマンスオーバーヘッドである.
=================================================================================================
Transientについて
1.transientの役割と使用方法
オブジェクトがSerilizableインタフェースを実装すれば、このオブジェクトをシーケンス化できることはよく知られています.javaのこのシーケンス化モードは開発者に多くの便利さを提供しています.特定のシーケンス化プロセスに関係なく、このクラスがSerilizableインタフェースを実装すれば、このクラスのすべての属性と方法が自動的にシーケンス化されます.
しかし、実際の開発の過程で、このクラスの一部の属性はシーケンス化する必要があり、他の属性はシーケンス化する必要はありません.例えば、ユーザーがパスワード、銀行カード番号などの機密情報を持っている場合、安全のためにネットワークで操作したくないという問題がよくあります.(主にシーケンス化操作に関し、ローカルシーケンス化キャッシュも適用される)で転送され、これらの情報に対応する変数にtransientキーワードを付けることができます.言い換えれば、このフィールドのライフサイクルは、ディスクに書き込まずに呼び出し元のメモリにのみ保存されます.
要するにjavaのtransientキーワードは便利で、Serilizableインタフェースを実現するだけで、シーケンス化を必要としない属性の前にキーワードtransientを追加し、オブジェクトをシーケンス化するとき、この属性は指定した目的地にシーケンス化されません.
例codeは次のとおりです.
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * @description   transient           
 *               ,                      
 *        
 * @author Alexia
 * @date  2013-10-15
 */
public class TransientTest {
    
    public static void main(String[] args) {
        
        User user = new User();
        user.setUsername("Alexia");
        user.setPasswd("123456");
        
        System.out.println("read before Serializable: ");
        System.out.println("username: " + user.getUsername());
        System.err.println("password: " + user.getPasswd());
        
        try {
            ObjectOutputStream os = new ObjectOutputStream(
                    new FileOutputStream("C:/user.txt"));
            os.writeObject(user); //  User      
            os.flush();
            os.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream(
                    "C:/user.txt"));
            user = (User) is.readObject(); //      User   
            is.close();
            
            System.out.println("
read after Serializable: "); System.out.println("username: " + user.getUsername()); System.err.println("password: " + user.getPasswd()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } class User implements Serializable { private static final long serialVersionUID = 8294180014912103005L; private String username; private transient String passwd; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; } }

出力:
read before Serializable: 
username: Alexia
password: 123456

read after Serializable: 
username: Alexia
password: null

パスワードフィールドはnullで、逆シーケンス化時にファイルから情報が取得されなかったことを示します.
2.transient使用小結
1)変数がtransientによって修飾されると、変数はオブジェクトの永続化の一部ではなく、シーケンス化後にアクセスできなくなります.
2)transientキーワードは変数のみを修飾でき、メソッドやクラスは修飾できません.ローカル変数はtransientキーワードで修飾できないことに注意してください.変数がユーザー定義クラス変数の場合、クラスはSerializableインタフェースを実装する必要があります.
3)transientキーワードで修飾された変数はシーケンス化されず,1つの静的変数はtransientで修飾されるかどうかにかかわらずシーケンス化されない.
3つ目は、Userクラスのusernameフィールドにstaticキーワードを付けると、プログラムの実行結果が変わらないこと、すなわちstaticタイプのusernameも「Alexia」と読み出されていることに気づいたため、迷っている人もいるかもしれませんが、3つ目の点と矛盾していませんか?実際には、3つ目の点は間違いありません(1つの静的変数はtransientによって修飾されるかどうかにかかわらず、シーケンス化できません)、逆シーケンス化後のクラスのstatic型変数usernameの値は現在のJVMのstatic変数に対応する値であり、この値はJVMの中で逆シーケンス化されていないので、信じませんか?では、証明します.
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * @description   transient           
 *               ,                      
 *        
 * @author Alexia
 * @date  2013-10-15
 */
public class TransientTest {
    
    public static void main(String[] args) {
        
        User user = new User();
        user.setUsername("Alexia");
        user.setPasswd("123456");
        
        System.out.println("read before Serializable: ");
        System.out.println("username: " + user.getUsername());
        System.err.println("password: " + user.getPasswd());
        
        try {
            ObjectOutputStream os = new ObjectOutputStream(
                    new FileOutputStream("C:/user.txt"));
            os.writeObject(user); //  User      
            os.flush();
            os.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            //          username  
            User.username = "jmwang";
            
            ObjectInputStream is = new ObjectInputStream(new FileInputStream(
                    "C:/user.txt"));
            user = (User) is.readObject(); //      User   
            is.close();
            
            System.out.println("
read after Serializable: "); System.out.println("username: " + user.getUsername()); System.err.println("password: " + user.getPasswd()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } class User implements Serializable { private static final long serialVersionUID = 8294180014912103005L; public static String username; private transient String passwd; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; } }

実行結果:
read before Serializable: 
username: Alexia
password: 123456

read after Serializable: 
username: jmwang
password: null

これは、逆シーケンス化後のクラスのstatic型変数usernameの値が現在のJVMのstatic変数に対応する値であり、シーケンス化時の値Alexiaではなく、修正後のjmwangであることを示しています.
3.transient使用の詳細--transientキーワードで修飾された変数は本当にシーケンス化できませんか?
次の例を考えてみましょう.
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

/**
 * @descripiton Externalizable     
 * 
 * @author Alexia
 * @date 2013-10-15
 *
 */
public class ExternalizableTest implements Externalizable {

    private transient String content = "  ,       ,      transient     ";

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(content);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        content = (String) in.readObject();
    }

    public static void main(String[] args) throws Exception {
        
        ExternalizableTest et = new ExternalizableTest();
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                new File("test")));
        out.writeObject(et);

        ObjectInput in = new ObjectInputStream(new FileInputStream(new File(
                "test")));
        et = (ExternalizableTest) in.readObject();
        System.out.println(et.content);

        out.close();
        in.close();
    }
}

content変数はシーケンス化されますか?はい、答えを全部負けました.はい、実行結果は次のとおりです.
  ,       ,      transient     

これはなぜでしょうか.クラスの変数がtransientキーワードで修飾されるとシーケンス化できないのではないでしょうか.
      Javaではオブジェクトのシーケンス化は2つのインタフェースを実装することで実現できることが知られており,Serializableインタフェースを実装するとすべてのシーケンス化が自動的に行われ,Externalizableインタフェースを実装すると自動シーケンス化できるものは何もなく,writeExternalメソッドで手動で指定するシーケンス化する変数が必要となる.これはtransientによって修飾されるかどうかとは関係ありません.したがって、第2の例はnullではなく変数content初期化の内容を出力する.
============================================================
原文1:
writeObjectとreadObjectとは?カスタマイズ可能なシーケンス化プロセス
原文2:
Java transientキーワード使用メモ