本章,我們對序列化進行深入的學習和探討。學習內容,包括序列化的作用、用途、用法,以及對實現序列化的2種方式Serializable和Externalizable的深入研究。
1. 序列化是的作用和用途
序列化,就是為了保存對象的狀態;而與之對應的反序列化,則可以把保存的對象狀態再讀出來。 簡言之:序列化/反序列化,是Java提供一種專門用於的保存/恢復對象狀態的機制。
一般在以下幾種情況下,我們可能會用到序列化: a)當你想把的內存中的對象狀態保存到一個文件中或者數據庫中時候; b)當你想用套接字在網絡上傳送對象的時候; c)當你想通過RMI傳輸對象的時候。
2. 演示程序1
下面,我們先通過一則簡單示例來查看序列化的用法。
源碼如下(SerialTest1.java):
/** * 序列化的演示測試程序 * * @author skywang */ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SerialTest1 { private static final String TMP_FILE = ".serialtest1.txt"; public static void main(String[] args) { // 將“對象”通過序列化保存 testWrite(); // 將序列化的“對象”讀出來 testRead(); } /** * 將Box對象通過序列化,保存到文件中 */ private static void testWrite() { try { // 獲取文件TMP_FILE對應的對象輸出流。 // ObjectOutputStream中,只能寫入“基本數據”或“支持序列化的對象” ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(TMP_FILE)); // 創建Box對象,Box實現了Serializable序列化接口 Box box = new Box("desk", 80, 48); // 將box對象寫入到對象輸出流out中,即相當於將對象保存到文件TMP_FILE中 out.writeObject(box); // 打印“Box對象” System.out.println("testWrite box: " + box); out.close(); } catch (Exception ex) { ex.printStackTrace(); } } /** * 從文件中讀取出“序列化的Box對象” */ private static void testRead() { try { // 獲取文件TMP_FILE對應的對象輸入流。 ObjectInputStream in = new ObjectInputStream( new FileInputStream(TMP_FILE)); // 從對象輸入流中,讀取先前保存的box對象。 Box box = (Box) in.readObject(); // 打印“Box對象” System.out.println("testRead box: " + box); in.close(); } catch (Exception e) { e.printStackTrace(); } } } /** * Box類“支持序列化”。因為Box實現了Serializable接口。 * * 實際上,一個類只需要實現Serializable即可實現序列化,而不需要實現任何函數。 */ class Box implements Serializable { private int width; private int height; private String name; public Box(String name, int width, int height) { this.name = name; this.width = width; this.height = height; } @Override public String toString() { return "["+name+": ("+width+", "+height+") ]"; } }
運行結果:
testWrite box: [desk: (80, 48) ] testRead box: [desk: (80, 48) ]
源碼說明:
(01) 程序的作用很簡單,就是演示:先將Box對象,通過對象輸出流保存到文件中;之後,再通過對象輸入流,將文件中保存的Box對象讀取出來。
(02) Box類說明。Box是我們自定義的演示類,它被用於序列化的讀寫。Box實現了Serialable接口,因此它支持序列化操作;即,Box支持通過ObjectOutputStream去寫入到輸出流中,並且支持通過ObjectInputStream從輸入流中讀取出來。
(03) testWrite()函數說明。testWrite()的作用就是,新建一個Box對象,然後將該Box對象寫入到文件中。 首先,新建文件TMP_FILE的文件輸出流對象(即FileOutputStream對象),再創建該文件輸出流的對象輸出流(即ObjectOutputStream對象)。 a) 關於FileInputStream和FileOutputStream的內容,可以參考“java io系列07之 FileInputStream和FileOutputStream”。 b) 關於ObjectInputStream和ObjectOutputStream的的更多知識,可以參考“java io系列05之 ObjectInputStream 和 ObjectOutputStream” 然後,新建Box對象。 最後,通過out.writeObject(box) 將box寫入到對象輸出流中。實際上,相當於將box寫入到文件TMP_FILE中。
(04) testRead()函數說明。testRead()的作用就是,從文件中讀出Box對象。 首先,新建文件TMP_FILE的文件輸入流對象(即FileInputStream對象),再創建該文件輸入流的對象輸入流(即ObjectInputStream對象)。 然後,通過in.readObject() 從對象輸入流中讀取出Box對象。實際上,相當於從文件TMP_FILE中讀取Box對象。
通過上面的示例,我們知道:我們可以自定義類,讓它支持序列化(即實現Serializable接口),從而能支持對象的保存/恢復。 若要支持序列化,除了“自定義實現Serializable接口的類”之外;java的“基本類型”和“java自帶的實現了Serializable接口的類”,都支持序列化。我們通過下面的示例去查看一下。
3. 演示程序2
源碼如下(SerialTest2.java):
/** * “基本類型” 和 “java自帶的實現Serializable接口的類” 對序列化的支持 * * @author skywang */ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Map; import java.util.HashMap; import java.util.Iterator; public class SerialTest2 { private static final String TMP_FILE = ".serialabletest2.txt"; public static void main(String[] args) { testWrite(); testRead(); } /** * ObjectOutputStream 測試函數 */ private static void testWrite() { try { ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(TMP_FILE)); out.writeBoolean(true); // 寫入Boolean值 out.writeByte((byte)65);// 寫入Byte值 out.writeChar('a'); // 寫入Char值 out.writeInt(20131015); // 寫入Int值 out.writeFloat(3.14F); // 寫入Float值 out.writeDouble(1.414D);// 寫入Double值 // 寫入HashMap對象 HashMap map = new HashMap(); map.put("one", "red"); map.put("two", "green"); map.put("three", "blue"); out.writeObject(map); out.close(); } catch (Exception ex) { ex.printStackTrace(); } } /** * ObjectInputStream 測試函數 */ private static void testRead() { try { ObjectInputStream in = new ObjectInputStream( new FileInputStream(TMP_FILE)); System.out.printf("boolean:%b\n" , in.readBoolean()); System.out.printf("byte:%d\n" , (in.readByte()&0xff)); System.out.printf("char:%c\n" , in.readChar()); System.out.printf("int:%d\n" , in.readInt()); System.out.printf("float:%f\n" , in.readFloat()); System.out.printf("double:%f\n" , in.readDouble()); // 讀取HashMap對象 HashMap map = (HashMap) in.readObject(); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry)iter.next(); System.out.printf("%-6s -- %s\n" , entry.getKey(), entry.getValue()); } in.close(); } catch (Exception e) { e.printStackTrace(); } } }
運行結果:
boolean:true byte:65 char:a int:20131015 float:3.140000 double:1.414000 two -- green one -- red three -- blue
源碼說明:
(01) 程序的作用很簡單,就是演示:先將“基本類型數據”和“HashMap對象”,通過對象輸出流保存到文件中;之後,再通過對象輸入流,將這些保存的數據讀取出來。
(02) testWrite()函數說明。testWrite()的作用就是,先將“基本類型數據”和“HashMap對象”,通過對象輸出流保存到文件中。 首先,新建文件TMP_FILE的文件輸出流對象(即FileOutputStream對象),再創建該文件輸出流的對象輸出流(即ObjectOutputStream對象)。 然後,通過 writeBoolean(), writeByte(), ... , writeDouble() 等一系列函數將“Boolean, byte, char, ... , double等基本數據類型”寫入到對象輸出流中。實際上,相當於將這些內容寫入到文件TMP_FILE中。 最後,新建HashMap對象map,並通過out.writeObject(map) 將map寫入到對象輸出流中。實際上,相當於map寫入到文件TMP_FILE中。 關於HashMap的更多知識,可以參考“Java 集合系列10之 HashMap詳細介紹(源碼解析)和使用示例”。
(03) testRead()函數說明。testRead()的作用就是,從文件中讀出testWrite()寫入的對象。 首先,新建文件TMP_FILE的文件輸入流對象(即FileInputStream對象),再創建該文件輸入流的對象輸入流(即ObjectInputStream對象)。 然後,通過in.readObject() 從對象輸入流中讀取出testWrite()對象。實際上,相當於從文件TMP_FILE中讀取出這些對象。
在前面,我們提到過:若要支持序列化,除了“自定義實現Serializable接口的類”之外;java的“基本類型”和“java自帶的實現了Serializable接口的類”,都支持序列化。為了驗證這句話,我們看看HashMap是否實現了Serializable接口。 HashMap是java.util包中定義的類,它的接口聲明如下:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {}
至此,我們對序列化的認識已經比較深入了:即知道了“序列化的作用和用法”,也知道了“基本類型”、“java自帶的支持Serializable接口的類”和“自定義實現Serializable接口的類”都能支持序列化。 應付序列化的簡單使用應該足夠了。但是,我們的目的是對序列化有更深層次的了解!更何況,寫此文的作者(也就是區區在下),應該比各位看官要累(既要寫代碼,又要總結,還得注意排版和用詞,講的通俗易懂,讓各位看得輕松自在);我這個菜鳥都能做到這些,何況對知識極其渴望的您呢?所以,請深吸一口氣,然後繼續……
我們在介紹序列化定義時,說過“序列化/反序列化,是專門用於的保存/恢復對象狀態的機制”。 從中,我們知道:序列化/反序列化,只支持保存/恢復對象狀態,即僅支持保存/恢復類的成員變量,但不支持保存類的成員方法! 但是,序列化是不是對類的所有的成員變量的狀態都能保存呢? 答案當然是否定的! (01) 序列化對static和transient變量,是不會自動進行狀態保存的。 transient的作用就是,用transient聲明的變量,不會被自動序列化。 (02) 對於Socket, Thread類,不支持序列化。若實現序列化的接口中,有Thread成員;在對該類進行序列化操作時,編譯會出錯! 這主要是基於資源分配方面的原因。如果Socket,Thread類可以被序列化,但是被反序列化之後也無法對他們進行重新的資源分配;再者,也是沒有必要這樣實現。
下面,我們還是通過示例來查看“序列化對static和transient的處理”。
4. 演示程序3
我們對前面的SerialTest1.java進行簡單修改,得到源文件(SerialTest3.java)如下:
/** * 序列化的演示測試程序 * * @author skywang */ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SerialTest3 { private static final String TMP_FILE = ".serialtest3.txt"; public static void main(String[] args) { // 將“對象”通過序列化保存 testWrite(); // 將序列化的“對象”讀出來 testRead(); } /** * 將Box對象通過序列化,保存到文件中 */ private static void testWrite() { try { // 獲取文件TMP_FILE對應的對象輸出流。 // ObjectOutputStream中,只能寫入“基本數據”或“支持序列化的對象” ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(TMP_FILE)); // 創建Box對象,Box實現了Serializable序列化接口 Box box = new Box("desk", 80, 48); // 將box對象寫入到對象輸出流out中,即相當於將對象保存到文件TMP_FILE中 out.writeObject(box); // 打印“Box對象” System.out.println("testWrite box: " + box); out.close(); } catch (Exception ex) { ex.printStackTrace(); } } /** * 從文件中讀取出“序列化的Box對象” */ private static void testRead() { try { // 獲取文件TMP_FILE對應的對象輸入流。 ObjectInputStream in = new ObjectInputStream( new FileInputStream(TMP_FILE)); // 從對象輸入流中,讀取先前保存的box對象。 Box box = (Box) in.readObject(); // 打印“Box對象” System.out.println("testRead box: " + box); in.close(); } catch (Exception e) { e.printStackTrace(); } } } /** * Box類“支持序列化”。因為Box實現了Serializable接口。 * * 實際上,一個類只需要實現Serializable即可實現序列化,而不需要實現任何函數。 */ class Box implements Serializable { private static int width; private transient int height; private String name; public Box(String name, int width, int height) { this.name = name; this.width = width; this.height = height; } @Override public String toString() { return "["+name+": ("+width+", "+height+") ]"; } }
SerialTest3.java 相比於 SerialTest1.java。僅僅對Box類中的 width 和 height 變量的定義進行了修改。 SerialTest1.java 中width和height定義
private int width; private int height;
SerialTest3.java 中width和height定義
查看本欄目
private static int width; private transient int height;
在看後面的結果之前,我們建議大家對程序進行分析,先自己得出一個結論。
運行結果:
testWrite box: [desk: (80, 48) ] testRead box: [desk: (80, 0) ]
結果分析:
我們前面說過,“序列化不對static和transient變量進行狀態保存”。因此,testWrite()中保存Box對象時,不會保存width和height的值。這點是毋庸置疑的!但是,為什麼testRead()中讀取出來的Box對象的width=80,而height=0呢? 先說,為什麼height=0。因為Box對象中height是int類型,而int類型的默認值是0。 再說,為什麼width=80。這是因為height是static類型,而static類型就意味著所有的Box對象都共用一個height值;而在testWrite()中,我們已經將height初始化為80了。因此,我們通過序列化讀取出來的Box對象的height值,也被就是80。
理解上面的內容之後,我們應該可以推斷出下面的代碼的運行結果。
源碼如下(SerialTest4.java):
/** * 序列化的演示測試程序 * * @author skywang */ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SerialTest4 { private static final String TMP_FILE = ".serialtest4.txt"; public static void main(String[] args) { // 將“對象”通過序列化保存 testWrite(); // 將序列化的“對象”讀出來 testRead(); } /** * 將Box對象通過序列化,保存到文件中 */ private static void testWrite() { try { // 獲取文件TMP_FILE對應的對象輸出流。 // ObjectOutputStream中,只能寫入“基本數據”或“支持序列化的對象” ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(TMP_FILE)); // 創建Box對象,Box實現了Serializable序列化接口 Box box = new Box("desk", 80, 48); // 將box對象寫入到對象輸出流out中,即相當於將對象保存到文件TMP_FILE中 out.writeObject(box); // 打印“Box對象” System.out.println("testWrite box: " + box); // 修改box的值 box = new Box("room", 100, 50); out.close(); } catch (Exception ex) { ex.printStackTrace(); } } /** * 從文件中讀取出“序列化的Box對象” */ private static void testRead() { try { // 獲取文件TMP_FILE對應的對象輸入流。 ObjectInputStream in = new ObjectInputStream( new FileInputStream(TMP_FILE)); // 從對象輸入流中,讀取先前保存的box對象。 Box box = (Box) in.readObject(); // 打印“Box對象” System.out.println("testRead box: " + box); in.close(); } catch (Exception e) { e.printStackTrace(); } } } /** * Box類“支持序列化”。因為Box實現了Serializable接口。 * * 實際上,一個類只需要實現Serializable即可實現序列化,而不需要實現任何函數。 */ class Box implements Serializable { private static int width; private transient int height; private String name; public Box(String name, int width, int height) { this.name = name; this.width = width; this.height = height; } @Override public String toString() { return "["+name+": ("+width+", "+height+") ]"; } }
SerialTest4.java 相比於 SerialTest3.java,在testWrite()中添加了一行代碼box = new Box("room", 100, 50);
運行結果:
testWrite box: [desk: (80, 48) ] testRead box: [desk: (100, 0) ]
現在,我們更加確認“序列化不對static和transient變量進行狀態保存”。但是,若我們想要保存static或transient變量,能不能辦到呢? 當然可以!我們在類中重寫兩個方法writeObject()和readObject()即可。下面程序演示了如何手動保存static和transient變量。
5. 演示程序4
我們對前面的SerialTest4.java進行簡單修改,以達到:序列化存儲static和transient變量的目的。
源碼如下(SerialTest5.java):
/** * 序列化的演示測試程序 * * @author skywang */ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.io.IOException; import java.lang.ClassNotFoundException; public class SerialTest5 { private static final String TMP_FILE = ".serialtest5.txt"; public static void main(String[] args) { // 將“對象”通過序列化保存 testWrite(); // 將序列化的“對象”讀出來 testRead(); } /** * 將Box對象通過序列化,保存到文件中 */ private static void testWrite() { try { // 獲取文件TMP_FILE對應的對象輸出流。 // ObjectOutputStream中,只能寫入“基本數據”或“支持序列化的對象” ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(TMP_FILE)); // 創建Box對象,Box實現了Serializable序列化接口 Box box = new Box("desk", 80, 48); // 將box對象寫入到對象輸出流out中,即相當於將對象保存到文件TMP_FILE中 out.writeObject(box); // 打印“Box對象” System.out.println("testWrite box: " + box); // 修改box的值 box = new Box("room", 100, 50); out.close(); } catch (Exception ex) { ex.printStackTrace(); } } /** * 從文件中讀取出“序列化的Box對象” */ private static void testRead() { try { // 獲取文件TMP_FILE對應的對象輸入流。 ObjectInputStream in = new ObjectInputStream( new FileInputStream(TMP_FILE)); // 從對象輸入流中,讀取先前保存的box對象。 Box box = (Box) in.readObject(); // 打印“Box對象” System.out.println("testRead box: " + box); in.close(); } catch (Exception e) { e.printStackTrace(); } } } /** * Box類“支持序列化”。因為Box實現了Serializable接口。 * * 實際上,一個類只需要實現Serializable即可實現序列化,而不需要實現任何函數。 */ class Box implements Serializable { private static int width; private transient int height; private String name; public Box(String name, int width, int height) { this.name = name; this.width = width; this.height = height; } private void writeObject(ObjectOutputStream out) throws IOException{ out.defaultWriteObject();//使定制的writeObject()方法可以利用自動序列化中內置的邏輯。 out.writeInt(height); out.writeInt(width); //System.out.println("Box--writeObject width="+width+", height="+height); } private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{ in.defaultReadObject();//defaultReadObject()補充自動序列化 height = in.readInt(); width = in.readInt(); //System.out.println("Box---readObject width="+width+", height="+height); } @Override public String toString() { return "["+name+": ("+width+", "+height+") ]"; } }
運行結果:
testWrite box: [desk: (80, 48) ] testRead box: [desk: (100, 0) ]
程序說明:
“序列化不會自動保存static和transient變量”,因此我們若要保存它們,則需要通過writeObject()和readObject()去手動讀寫。 (01) 通過writeObject()方法,寫入要保存的變量。writeObject的原始定義是在ObjectOutputStream.java中,我們按照如下示例覆蓋即可:
private void writeObject(ObjectOutputStream out) throws IOException{ out.defaultWriteObject();// 使定制的writeObject()方法可以利用自動序列化中內置的邏輯。 out.writeInt(ival); // 若要保存“int類型的值”,則使用writeInt() out.writeObject(obj); // 若要保存“Object對象”,則使用writeObject() }
(02) 通過readObject()方法,讀取之前保存的變量。readObject的原始定義是在ObjectInputStream.java中,我們按照如下示例覆蓋即可:
private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{ in.defaultReadObject(); // 使定制的readObject()方法可以利用自動序列化中內置的邏輯。 int ival = in.readInt(); // 若要讀取“int類型的值”,則使用readInt() Object obj = in.readObject(); // 若要讀取“Object對象”,則使用readObject() }
至此,我們就介紹完了“序列化對static和transient變量的處理”。 接下來,我們來研究“對於Socket, Thread類,不支持序列化”。還是通過示例來查看。
6. 演示程序5
我們修改SerialTest5.java的源碼,在Box類中添加一個Thread成員。
源碼如下(SerialTest6.java):
/** * 序列化的演示測試程序 * * @author skywang */ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.Thread; import java.io.IOException; import java.lang.ClassNotFoundException; public class SerialTest6 { private static final String TMP_FILE = ".serialtest6.txt"; public static void main(String[] args) { // 將“對象”通過序列化保存 testWrite(); // 將序列化的“對象”讀出來 testRead(); } /** * 將Box對象通過序列化,保存到文件中 */ private static void testWrite() { try { // 獲取文件TMP_FILE對應的對象輸出流。 // ObjectOutputStream中,只能寫入“基本數據”或“支持序列化的對象” ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(TMP_FILE)); // 創建Box對象,Box實現了Serializable序列化接口 Box box = new Box("desk", 80, 48); // 將box對象寫入到對象輸出流out中,即相當於將對象保存到文件TMP_FILE中 out.writeObject(box); // 打印“Box對象” System.out.println("testWrite box: " + box); // 修改box的值 box = new Box("room", 100, 50); out.close(); } catch (Exception ex) { ex.printStackTrace(); } } /** * 從文件中讀取出“序列化的Box對象” */ private static void testRead() { try { // 獲取文件TMP_FILE對應的對象輸入流。 ObjectInputStream in = new ObjectInputStream( new FileInputStream(TMP_FILE)); // 從對象輸入流中,讀取先前保存的box對象。 Box box = (Box) in.readObject(); // 打印“Box對象” System.out.println("testRead box: " + box); in.close(); } catch (Exception e) { e.printStackTrace(); } } } /** * Box類“支持序列化”。因為Box實現了Serializable接口。 * * 實際上,一個類只需要實現Serializable即可實現序列化,而不需要實現任何函數。 */ class Box implements Serializable { private static int width; private transient int height; private String name; private Thread thread = new Thread() { @Override public void run() { System.out.println("Serializable thread"); } }; public Box(String name, int width, int height) { this.name = name; this.width = width; this.height = height; } private void writeObject(ObjectOutputStream out) throws IOException{ out.defaultWriteObject();//使定制的writeObject()方法可以利用自動序列化中內置的邏輯。 out.writeInt(height); out.writeInt(width); //System.out.println("Box--writeObject width="+width+", height="+height); } private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{ in.defaultReadObject();//defaultReadObject()補充自動序列化 height = in.readInt(); width = in.readInt(); //System.out.println("Box---readObject width="+width+", height="+height); } @Override public String toString() { return "["+name+": ("+width+", "+height+") ]"; } }
結果是,編譯出錯!
事實證明,不能對Thread進行序列化。若希望程序能編譯通過,我們對Thread變量添加static或transient修飾即可!如下,是對Thread添加transient修飾的源碼(SerialTest7.java):
/** * 序列化的演示測試程序 * * @author skywang */ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.Thread; import java.io.IOException; import java.lang.ClassNotFoundException; public class SerialTest7 { private static final String TMP_FILE = ".serialtest7.txt"; public static void main(String[] args) { // 將“對象”通過序列化保存 testWrite(); // 將序列化的“對象”讀出來 testRead(); } /** * 將Box對象通過序列化,保存到文件中 */ private static void testWrite() { try { // 獲取文件TMP_FILE對應的對象輸出流。 // ObjectOutputStream中,只能寫入“基本數據”或“支持序列化的對象” ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(TMP_FILE)); // 創建Box對象,Box實現了Serializable序列化接口 Box box = new Box("desk", 80, 48); // 將box對象寫入到對象輸出流out中,即相當於將對象保存到文件TMP_FILE中 out.writeObject(box); // 打印“Box對象” System.out.println("testWrite box: " + box); // 修改box的值 box = new Box("room", 100, 50); out.close(); } catch (Exception ex) { ex.printStackTrace(); } } /** * 從文件中讀取出“序列化的Box對象” */ private static void testRead() { try { // 獲取文件TMP_FILE對應的對象輸入流。 ObjectInputStream in = new ObjectInputStream( new FileInputStream(TMP_FILE)); // 從對象輸入流中,讀取先前保存的box對象。 Box box = (Box) in.readObject(); // 打印“Box對象” System.out.println("testRead box: " + box); in.close(); } catch (Exception e) { e.printStackTrace(); } } } /** * Box類“支持序列化”。因為Box實現了Serializable接口。 * * 實際上,一個類只需要實現Serializable即可實現序列化,而不需要實現任何函數。 */ class Box implements Serializable { private static int width; private transient int height; private String name; private transient Thread thread = new Thread() { @Override public void run() { System.out.println("Serializable thread"); } }; public Box(String name, int width, int height) { this.name = name; this.width = width; this.height = height; } private void writeObject(ObjectOutputStream out) throws IOException{ out.defaultWriteObject();//使定制的writeObject()方法可以利用自動序列化中內置的邏輯。 out.writeInt(height); out.writeInt(width); //System.out.println("Box--writeObject width="+width+", height="+height); } private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{ in.defaultReadObject();//defaultReadObject()補充自動序列化 height = in.readInt(); width = in.readInt(); //System.out.println("Box---readObject width="+width+", height="+height); } @Override public String toString() { return "["+name+": ("+width+", "+height+") ]"; } }
至此,關於“Serializable接口”來實現序列化的內容,都說完了。為什麼這麼說?因為,實現序列化,除了Serializable之外,還有其它的方式,就是通過實現Externalizable來實現序列化。整理下心情,下面繼續對Externalizable進行了解。
7. Externalizable和完全定制序列化過程
如果一個類要完全負責自己的序列化,則實現Externalizable接口,而不是Serializable接口。
Externalizable接口定義包括兩個方法writeExternal()與readExternal()。需要注意的是:聲明類實現Externalizable接口會有重大的安全風險。writeExternal()與readExternal()方法聲明為public,惡意類可以用這些方法讀取和寫入對象數據。如果對象包含敏感信息,則要格外小心。
下面,我們修改之前的SerialTest1.java測試程序;將其中的Box由“實現Serializable接口” 改為 “實現Externalizable接口”。 修改後的源碼如下( ExternalizableTest1.java):
/** * 序列化的演示測試程序 * * @author skywang */ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectOutput; import java.io.ObjectInput; import java.io.Serializable; import java.io.Externalizable; import java.io.IOException; import java.lang.ClassNotFoundException; public class ExternalizableTest1 { private static final String TMP_FILE = ".externalizabletest1.txt"; public static void main(String[] args) { // 將“對象”通過序列化保存 testWrite(); // 將序列化的“對象”讀出來 testRead(); } /** * 將Box對象通過序列化,保存到文件中 */ private static void testWrite() { try { // 獲取文件TMP_FILE對應的對象輸出流。 // ObjectOutputStream中,只能寫入“基本數據”或“支持序列化的對象” ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(TMP_FILE)); // 創建Box對象 Box box = new Box("desk", 80, 48); // 將box對象寫入到對象輸出流out中,即相當於將對象保存到文件TMP_FILE中 out.writeObject(box); // 打印“Box對象” System.out.println("testWrite box: " + box); out.close(); } catch (Exception ex) { ex.printStackTrace(); } } /** * 從文件中讀取出“序列化的Box對象” */ private static void testRead() { try { // 獲取文件TMP_FILE對應的對象輸入流。 ObjectInputStream in = new ObjectInputStream( new FileInputStream(TMP_FILE)); // 從對象輸入流中,讀取先前保存的box對象。 Box box = (Box) in.readObject(); // 打印“Box對象” System.out.println("testRead box: " + box); in.close(); } catch (Exception e) { e.printStackTrace(); } } } /** * Box類實現Externalizable接口 */ class Box implements Externalizable { private int width; private int height; private String name; public Box() { } public Box(String name, int width, int height) { this.name = name; this.width = width; this.height = height; } @Override public void writeExternal(ObjectOutput out) throws IOException { } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { } @Override public String toString() { return "["+name+": ("+width+", "+height+") ]"; } }
運行結果:
testWrite box: [desk: (80, 48) ] testRead box: [null: (0, 0) ]
說明:
(01) 實現Externalizable接口的類,不會像實現Serializable接口那樣,會自動將數據保存。 (02) 實現Externalizable接口的類,必須實現writeExternal()和readExternal()接口! 否則,程序無法正常編譯! (03) 實現Externalizable接口的類,必須定義不帶參數的構造函數! 否則,程序無法正常編譯! (04) writeExternal() 和 readExternal() 的方法都是public的,不是非常安全!
接著,我們修改上面的ExternalizableTest1.java測試程序;實現Box類中的writeExternal()和readExternal()接口! 修改後的源碼如下( ExternalizableTest2.java):
/** * 序列化的演示測試程序 * * @author skywang */ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectOutput; import java.io.ObjectInput; import java.io.Serializable; import java.io.Externalizable; import java.io.IOException; import java.lang.ClassNotFoundException; public class ExternalizableTest2 { private static final String TMP_FILE = ".externalizabletest2.txt"; public static void main(String[] args) { // 將“對象”通過序列化保存 testWrite(); // 將序列化的“對象”讀出來 testRead(); } /** * 將Box對象通過序列化,保存到文件中 */ private static void testWrite() { try { // 獲取文件TMP_FILE對應的對象輸出流。 // ObjectOutputStream中,只能寫入“基本數據”或“支持序列化的對象” ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(TMP_FILE)); // 創建Box對象 Box box = new Box("desk", 80, 48); // 將box對象寫入到對象輸出流out中,即相當於將對象保存到文件TMP_FILE中 out.writeObject(box); // 打印“Box對象” System.out.println("testWrite box: " + box); out.close(); } catch (Exception ex) { ex.printStackTrace(); } } /** * 從文件中讀取出“序列化的Box對象” */ private static void testRead() { try { // 獲取文件TMP_FILE對應的對象輸入流。 ObjectInputStream in = new ObjectInputStream( new FileInputStream(TMP_FILE)); // 從對象輸入流中,讀取先前保存的box對象。 Box box = (Box) in.readObject(); // 打印“Box對象” System.out.println("testRead box: " + box); in.close(); } catch (Exception e) { e.printStackTrace(); } } } /** * Box類實現Externalizable接口 */ class Box implements Externalizable { private int width; private int height; private String name; public Box() { } public Box(String name, int width, int height) { this.name = name; this.width = width; this.height = height; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); out.writeInt(width); out.writeInt(height); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); width = in.readInt(); height = in.readInt(); } @Override public String toString() { return "["+name+": ("+width+", "+height+") ]"; } }
運行結果:
testWrite box: [desk: (80, 48) ]testRead box: [desk: (80, 48) ]
至此,序列化的內容就全部講完了。
來源:http://www.cnblogs.com/skywang12345/p/io_06.html