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(),在此方法中必須手動的恢復數據。
文章列表