文章出處

Java默認的序列化機制非常簡單,而且序列化后的對象不需要再次調用構造器重新生成,但是在實際中,我們可以會希望對象的某一部分不需要被序列化,或者說一個對象被還原之后,其內部的某些子對象需要重新創建,從而不必將該子對象序列化。 在這些情況下,我們可以考慮實現Externalizable接口從而代替Serializable接口來對序列化過程進行控制(后面我們會講到一個更簡單的方式,通過transient的方式)。

     Externalizable接口extends Serializable接口,而且在其基礎上增加了兩個方法:writeExternal()和readExternal()。這兩個方法會在序列化和反序列化還原的過程中被自動調用,以便執行一些特殊的操作。

復制代碼
package java.io;

import java.io.ObjectOutput;
import java.io.ObjectInput;


public interface Externalizable extends java.io.Serializable {
   
    void writeExternal(ObjectOutput out) throws IOException;

    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
復制代碼

     下面這段代碼示范了如何完整的保存和恢復一個Externalizable對象

復制代碼
package test.serializable;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Blip implements Externalizable {
    
    private int i ;
    
    private String s;//沒有初始化
    
    public Blip() {
         //默認構造函數必須有,而且必須是public
        System.out.println("Blip默認構造函數");
    }
    
    public Blip(String s ,int i) {
        //s,i只是在帶參數的構造函數中進行初始化。
        System.out.println("Blip帶參數構造函數");
        this.s = s;
        this.i = i;
    }
    
    public String toString() {
        return s  + i ;
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        System.out.println("調用readExternal()方法");
        s = (String)in.readObject();//在反序列化時,需要初始化s和i,否則只是調用默認構造函數,得不到s和i的值
        i = in.readInt();
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("調用writeExternal()方法");
        out.writeObject(s); //如果我們不將s和i的值寫入的話,那么在反序列化的時候,就不會得到這些值。
        out.writeInt(i);
    }

}
復制代碼
復制代碼
package test.serializable;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class ExternalizableTest {

    /**
     * @param args
     * @throws IOException 
     * @throws ClassNotFoundException 
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        System.out.println("序列化之前");
        Blip b = new Blip("This String is " , 47);
        System.out.println(b);
        
        System.out.println("序列化操作,writeObject");
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(out);
        oos.writeObject(b);
        System.out.println("反序列化之后,readObject");
        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(in);
        Blip bb = (Blip)ois.readObject();
        System.out.println(bb);     
    }
}
復制代碼

運行結果如下所示:     

復制代碼
序列化之前
Blip帶參數構造函數
This String is 47
序列化操作,writeObject
調用writeExternal()方法
反序列化之后,readObject
Blip默認構造函數
調用readExternal()方法
This String is 47
復制代碼

分析結果:

     在Blip類中,字段s和i只在第二個構造器中初始化,而不是在默認的構造器其中初始化的,每次writeObject時,都會調用WriteExtenal()方法,而在WriteExtenal()方法中我們需要將當前對象的值寫入到流中;而每次readObject()時,調用的是默認的構造函數,如果我們不在 readExternal()方法中初始化s和i,那么s就會為null,而i就會為0。

下面分幾種情況討論:

  1) 如果我們只修改writeExternal()方法如下:

@Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("調用writeExternal()方法");
//        out.writeObject(s);
//        out.writeInt(i);
    }

那么運行的結果為:

復制代碼
序列化之前
Blip帶參數構造函數
This String is 47
序列化操作,writeObject
調用writeExternal()方法
反序列化之后,readObject
Blip默認構造函數
調用readExternal()方法
Exception in thread "main" java.io.OptionalDataException
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1349)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
    at test.serializable.Blip.readExternal(Blip.java:34)
    at java.io.ObjectInputStream.readExternalData(ObjectInputStream.java:1792)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1751)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
    at test.serializable.ExternalizableTest.main(ExternalizableTest.java:28)
復制代碼

      原因是因為,我們在ObjectOutPutStream中沒有writeObject,而在ObjectInputStream中readObject導致的

 2)如果我們修改writeExternal()方法如下:

@Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("調用writeExternal()方法");
        out.writeObject("自己定義的");
        out.writeInt(250);
    }

那么運行的結果為:

復制代碼
序列化之前
Blip帶參數構造函數
This String is 47
序列化操作,writeObject
調用writeExternal()方法
反序列化之后,readObject
Blip默認構造函數
調用readExternal()方法
自己定義的250
復制代碼

看見沒,反序列化后得到的s和i是我們在writeExternal()中自定義的數據

  3) 如果我們只是修改readExternal()方法

復制代碼
    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        System.out.println("調用readExternal()方法");
//        s = (String)in.readObject();
//        i = in.readInt();
    }
復制代碼

那么運行的結果為:

復制代碼
序列化之前
Blip帶參數構造函數
This String is 47
序列化操作,writeObject
調用writeExternal()方法
反序列化之后,readObject
Blip默認構造函數
調用readExternal()方法
null0
復制代碼

   看見沒?最后一行打印的是null0,說明沒有對s和i進行初始化。

4)如果我們刪除Blip的默認構造函數,或者將其權限不設置為public

復制代碼
//    public Blip() {
//         //默認構造函數必須有,而且必須是public
//        System.out.println("Blip默認構造函數");
//    }
//    or
     Blip() {
         //默認構造函數必須有,而且必須是public
        System.out.println("Blip默認構造函數");
    }
復制代碼

運行結果如下:

復制代碼
序列化之前
Blip帶參數構造函數
This String is 47
序列化操作,writeObject
調用writeExternal()方法
反序列化之后,readObject
Exception in thread "main" java.io.InvalidClassException: test.serializable.Blip; test.serializable.Blip; no valid constructor
    at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:713)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1733)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
    at test.serializable.ExternalizableTest.main(ExternalizableTest.java:28)
Caused by: java.io.InvalidClassException: test.serializable.Blip; no valid constructor
    at java.io.ObjectStreamClass.<init>(ObjectStreamClass.java:471)
    at java.io.ObjectStreamClass.lookup(ObjectStreamClass.java:310)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1106)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:326)
    at test.serializable.ExternalizableTest.main(ExternalizableTest.java:24)
復制代碼

  在反序列化時,會出現無效的構造函數這個錯誤,可見必須有權限為public的默認的構造器(如果有非默認的帶參數的構造函數,那么必須顯示的寫出默認的構造函數,如果沒有非默認的構造函數,那么默認構造函數可以不顯示的寫出來),才能使Externalizable對象產生正確的行為。

總結Externalizable對象的用法:

    與Serizable對象不同,使用Externalizabled,就意味著沒有任何東西可以自動序列化, 為了正常的運行,我們需要在writeExtenal()方法中將自對象的重要信息寫入,從而手動的完成序列化。對于一個Externalizabled對象,對象的默認構造函數都會被調用(包括哪些在定義時已經初始化的字段),然后調用readExternal(),在此方法中必須手動的恢復數據。


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()