あなたが知らないJavaシーケンス化


Javaシーケンス化は、実行時のオブジェクトステータス(オブジェクトインスタンスドメインの値)、すなわち、よく言われるオブジェクト永続化を記録することができることを知っています.この過程は実は非常に複雑で、ここではJavaのオブジェクトシーケンス化をよく理解します.
 
1、まず、Javaオブジェクトのシーケンス化は、privateプライベートドメインを含むオブジェクトのインスタンスドメインデータを永続化して格納することであることを明らかにします.オブジェクト全体が属するクラス情報を格納するのではなく、実はJVMを知っていれば、私たちはそれを理解することができます.実際にスタックに格納されているオブジェクトには、インスタンスドメインのデータ値とクラス情報を指すアドレスが含まれていますが、オブジェクトが属するクラス情報はメソッド領域に格納されます.永続レイヤデータをオブジェクトに逆シーケンス化する場合は、インスタンスドメインデータ値を新しく作成したオブジェクトに格納するだけです.
 
2、シーケンス化するクラスはすべてSerializableインタフェースを実現しなければならないことを知っています.しかし、すべてのクラスがシーケンス化できるのではないでしょうか.もちろんそうではありません.シーケンス化がオブジェクトのプライベートデータドメインに簡単に触れることができることを考えてみてください.これはどんなに危険な脆弱性ですか.まとめると、JDKには4種類のクラスオブジェクトが絶対にシーケンス化できない.
(1)下位実装クラス(too closely tied to native code)に依存しすぎる.例えばjava.util.zip.Deflater. 
(2)オブジェクトの状態は,仮想マシンの内部や変化するランタイム環境に依存する.例えばjava.lang.Thread, java.io.InputStream(3)は潜在的なセキュリティ問題に関与する.例えばjava.lang.SecurityManager, java.security.MessageDigest(4)はすべて静的ドメインのクラスであり,オブジェクトインスタンスデータはない.静的ドメイン自体もメソッド領域に格納されていることを知っておく必要があります.
 
3、カスタムクラスはSerializableインタフェースを実現すれば、すべてシーケンス化できるのではないでしょうか.もちろんそうではありません.次の例を見てみましょう.
class Employee implements Serializable{
         private ZipFile zf=null;
         Employee(ZipFile zf){
                this.zf=zf;
         } 
}

ObjectOutputStream oout=
new ObjectOutputStream(new FileInputStream(new File("aaa.txt")));
oout.writeObject(new Employee(new ZipFile("c://.."));

実行後にjavaを投げ出すことがわかります.io.NotSerializableException : java.util.zip.ZipFile .明らかに、Employeeオブジェクトをシーケンス化するには、JDKではシーケンス化できないデータドメインZipFileオブジェクトもシーケンス化する必要があります.したがって,シーケンス化不可能なオブジェクトドメインを含むオブジェクトもシーケンス化できない.実際には不可能ではありませんが、以下の6点でお話しします.
 
4、シーケンス可能なクラスがシーケンス化に成功した後、必ず逆シーケンス化できるのではないでしょうか.(ここでは、デフォルトでは同じ環境で、クラス定義は互換性を満たすために永遠に変更されません.シーケンス化された互換性について説明します).答えは必ずしもそうではありませんよ.列を見てみましょう
//         
class Employee{ 
	private String name;
	Employee(String n){
		this.name=n;
	}
	public String getName(){
		return this.name;
	}
}
//         
class Manager extends Employee implements Serializable{
	private int id;
	Manager(String name, int id){
		super(name);
		this.id=id;
	}
}
//          
public static void main(String[] args) throws IOException, ClassNotFoundException{
         File file=new File("E:/aaa.txt");
	ObjectOutputStream oout=new ObjectOutputStream(new FileOutputStream(file));
	oout.writeObject(new Manager("amao",123));
	oout.close();
	System.out.println("     ");
		
	ObjectInputStream oin=new ObjectInputStream(new FileInputStream(file));
	Object o=oin.readObject();
	oin.close();
	System.out.println("      :"+((Manager) o).getName());
}

プログラムの実行結果は、「シーケンス化成功」を印刷してjavaを投げ出す.io.InvalidClassException: Manager; Manager; no valid constructor. なぜこんなことになったのでしょうか.シーケンス化時にマネージャクラスオブジェクトのデータドメインidをファイルに書き込むだけであることは明らかですが、逆シーケンス化の過程でスタックにマネージャの新しいオブジェクトを作成する必要があります.どのクラスオブジェクトの作成も、まず親のコンストラクタを呼び出して親を初期化する必要があることはよく知られていますが、シーケンス化ファイルに親のEmployeeのnameデータがないと、データがないためにEmployee(String)コンストラクタを呼び出すと例外が発生します.データがない以上、無パラメトリックコンストラクタを呼び出してもいいですか?実際には、Employee()無パラメトリックコンストラクタが存在する場合、例外は放出されませんが、印刷を実行するときに「逆シーケンス化成功:null」が発生します.
要約すると、現在のクラスのすべてのスーパークラスに1つのクラスがシーケンス化されず、パラメトリックコンストラクタもありません.現在のクラスは逆シーケンス化できません.パラメトリックコンストラクタがない場合、このスーパークラスの逆シーケンス化されたデータドメインはnullまたは0、falseなどになります.
 
5、直列化の互換性の問題!
クラス定義は、JDK 1.1からJDK 1.2へのHashTableの変更など、継続的に人為的に更新される可能性が高い.では、以前にシーケンス化された古いクラスオブジェクトは、新しいクラスオブジェクトに逆シーケンス化できない可能性があります.これがシーケンス化された互換性の問題であり,厳密にはクラスのstaticとtransient以外のすべての部分を変更すると互換性の問題をもたらす.JDKはstream unique identifier(SUID)を用いて互換性を識別した.SUIDは、複雑な関数によって計算されるクラス名、インタフェース名、メソッド、およびデータドメインの64ビットhash値である.この値はクラス内の静的ドメインに格納されます.
                               private static final long serialVersionUID = 3487495895819393L
クラスの定義を少し変えるだけで、このクラスのSUIDが変わります.次の手順で見てみましょう.
//    Employee
class Employee implements Serializable{
	private String name;
	Employee(String n){
		this.name=n;
	}
	public String getName(){
		return this.name;
	}
}
//  ,  SUID=5135178525467874279L
long serialVersionUID=ObjectStreamClass.lookup(Class.forName("Employee")).getSerialVersionUID();
System.out.println(serialVersionUID);

//    Employee
class Employee implements Serializable{
	private String name1; //  ,              
	Employee(String n){
		this.name1=n;
	}
	public String getName(){
		return this.name1;
	}
}
//  ,  SUID=-2226350316230217613L
long serialVersionUID=ObjectStreamClass.lookup(Class.forName("Employee")).getSerialVersionUID();
System.out.println(serialVersionUID);

2回のテストのSUIDは異なりますが、nameドメインがstaticまたはtransientで宣言されている場合、このドメイン名を変更してもSUIDに影響しません.
明らかに、JVMは、新旧のSUIDの違いを検出することによって、シーケンス化オブジェクトと逆シーケンス化オブジェクトとの互換性が検出される.javaを投げ出すio.InvalidClassException: Employee; local class incompatible:
クラス定義の変更は必須である場合が多いが、シーケンス化の不互換性は望ましくない.クラスに表示されるserialVersionUIDを定義し、明確なlong値を与えることができます.これにより、JVMのデフォルトの互換性チェックが回避されます.しかし、データドメイン名の変更によって逆シーケンス化が発生すると、変更されたデータドメインはデフォルトのnullまたは0またはfalse値しか得られません.
 
6、上記第3点では、シーケンス化できないZipFileオブジェクト参照のデータドメインが含まれているため、シーケンス化できないEmployeeのカラムサブについて説明した.しかし、ZipFileに対応するローカルファイルパスをシーケンス化したい場合がありますが、本当に仕方がないのではないでしょうか.ここでは非常に有用な応用について説明します.
クラスオブジェクトをwriteObject(Object)メソッドでシーケンス化する必要がある場合、まず、このクラスオブジェクトのすべてのスーパークラスについて継承階層の高さから低さに従って各スーパークラスのデータドメインを書きます.誰がすべてのスーパークラスがSerializableインタフェースを実現したことを保証することができますか?実際には、シーケンス化できないクラスについて、JVMはこれらのクラスにこのような方法があるかどうかを確認します.
private void writeObject(Object OutputStreamout)throws IOExceptionがある場合、JVMはこのメソッドを呼び出してクラスのデータドメインをシーケンス化します.JDKのObjectOutputStreamクラスの実装を見てみましょう(ここではソースコードの実行手順のみを示します):
//             
writeObject(Object); 

//ObjectOutputStream writeObject  
public final void writeObject(Object obj) throws IOException { 
        writeObject0(obj, false);
}

//ObjectOutputStream,     Object   
private void writeObject0(Object obj, boolean unshared) {
       if (obj instanceof Serializable) {
		writeOrdinaryObject(obj, desc, unshared);
}

//ObjectOutputStream
private void writeOrdinaryObject(Object obj, ObjectStreamClass desc,  boolean unshared) {
       writeSerialData(obj, desc);
}

//ObjectOutputStream,                ,     
 private void writeSerialData(Object obj, ObjectStreamClass desc)  throws IOException{
         //     writeObject(ObjectOutputStream)  ,         
         if (slotDesc.hasWriteObjectMethod()) { 
                slotDesc.invokeWriteObject(obj, this);
         }//       ,              。
         else {//                      ,                   writeObject(ObjectOutputStream)      ,       。
		defaultWriteFields(obj, slotDesc);
	 }
}

ObjectOutputStreamのwriteSerialData()メソッドは、JVMチェックwriteObject(ObjectOutputStreamout)というプライベートメソッドの潜在的な実行メカニズムを説明します.すなわち,シーケンス化できなかったクラスの一部のデータドメインをシーケンス化できるように,この方法を構築することができる.では、ZipFileのシーケンス化を始めましょう.
//           ZipFile,         JDK  ZipFile,           。
class SerializableZipFile implements Serializable{
	public ZipFile zf;
	//    ZipFile  
	SerializableZipFile(String filename) throws IOException{
		zf=new ZipFile(filename);
	}
	// ZipFile          ,    String   
	private void writeObject(ObjectOutputStream out)throws IOException{
		out.writeObject(zf.getName());
	}
	//   ,       JVM             。
	private void readObject(ObjectInputStream in)throws IOException,ClassNotFoundException{
		String filename=(String)in.readObject();
		zf=new ZipFile(filename);
	}
}
//  
public static void main(String[] args) throws IOException, ClassNotFoundException{
	//   
        File file=new File("E:/aaa.txt");
	ObjectOutputStream oout=new ObjectOutputStream(new FileOutputStream(file));
	oout.writeObject(new SerializableZipFile("e:/aaa.zip"));
	oout.close();
	System.out.println("     ");
	//    
	ObjectInputStream oin=new ObjectInputStream(new FileInputStream(file));
	Object o=oin.readObject();
	oin.close();
	System.out.println("      :"+((SerializableZipFile) o).zf.getName());
}
//     
//      :e:\aaa.zip

素晴らしいですね.シーケンス可能なZipFileクラスを構築しました.これは本当に偉大なことだ.