對(duì)象序列化的目標(biāo)是將對(duì)象保存在磁盤(pán)中或者在網(wǎng)絡(luò)中進(jìn)行傳輸。實(shí)現(xiàn)的機(jī)制是允許將對(duì)象轉(zhuǎn)為與平臺(tái)無(wú)關(guān)的二進(jìn)制流。
java中對(duì)象的序列化機(jī)制是將允許對(duì)象轉(zhuǎn)為字節(jié)序列。這些字節(jié)序列可以使Java對(duì)象脫離程序存在,從而可以保存在磁盤(pán)上,也可以在網(wǎng)絡(luò)間傳輸。
對(duì)象的序列化是將一個(gè)Java對(duì)象寫(xiě)入IO流;與此對(duì)應(yīng)的,反序列化則是從IO流中恢復(fù)一個(gè)Java對(duì)象。
實(shí)現(xiàn)序列化如果要將一個(gè)java對(duì)象序列化,那么對(duì)象的類(lèi)需要是可序列化的。要讓類(lèi)可序列化,那么這個(gè)類(lèi)需要實(shí)現(xiàn)如下兩個(gè)接口:
實(shí)現(xiàn)Serializable接口非常簡(jiǎn)單,只要讓java實(shí)現(xiàn)Serializable接口即可,無(wú)需實(shí)現(xiàn)任何方法。
一個(gè)類(lèi)一旦實(shí)現(xiàn)了Serializable接口,那么該類(lèi)的對(duì)象就是可序列化的。實(shí)現(xiàn)類(lèi)的對(duì)象的序列化可以使用ObjectOutputStream,實(shí)現(xiàn)步驟如下:
以下是一個(gè)實(shí)例:
package com.zhyea.test;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.io.Serializable;/** * 序列化測(cè)試類(lèi) * * @author robin * @date 2014年12月18日 */public class SerialTest { public static void main(String[] args) { ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream("D://object.txt")); Person robin = new Person("robin", 29); oos.writeObject(robin); } catch (IOException e) { e.PRintStackTrace(); } finally { if (null != oos) { try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } } }}/** * 序列化測(cè)試用對(duì)象 * * @author robin * @date 2014年12月18日 */class Person implements Serializable{ private static final long serialVersionUID = -6412852654889352693L; /** * 姓名 */ private String name; /** * 年齡 */ private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}如上的代碼實(shí)現(xiàn)了將一個(gè)Person對(duì)象保存在了磁盤(pán)的一個(gè)文本文件object.txt上。運(yùn)行程序在D盤(pán)上生成了一個(gè)object.txt文件。以下是文件內(nèi)容:

有亂碼(字節(jié)流轉(zhuǎn)字符流導(dǎo)致的),但仍不影響我們分辨出里面是不是我們保存的對(duì)象。
接下來(lái)需要反序列化將Person對(duì)象從磁盤(pán)上讀出。相應(yīng)的反序列化需要使用的類(lèi)是ObjectInputStream,反序列化步驟如下:
接下來(lái),重構(gòu)下我們的代碼,實(shí)現(xiàn)反序列化,如下:
package com.zhyea.test;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;/** * 序列化測(cè)試類(lèi) * * @author robin * @date 2014年12月18日 */public class SerialTest { public static void main(String[] args) { Person robin = new Person("robin", 29); String savePath = "D://object.txt"; SerialTest test = new SerialTest(); try { test.serialize(robin, savePath); Person person = (Person) test.deSerialize(savePath); System.out.println("Name:" + person.getName() + " Age:" + person.getAge()); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 實(shí)現(xiàn)序列化 * * @param obj * 要被序列化保存的對(duì)象 * @param path * 保存地址 * @throws IOException */ public void serialize(Object obj, String path) throws IOException { ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream(path)); oos.writeObject(obj); } finally { if (null != oos) oos.close(); } } /** * 反序列化取出對(duì)象 * * @param path * 被序列化對(duì)象保存的位置 * @return * @throws IOException * @throws ClassNotFoundException */ public Object deSerialize(String path) throws IOException, ClassNotFoundException { ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream(path)); return ois.readObject(); } finally { if (null != ois) ois.close(); } }}/** * 序列化測(cè)試用對(duì)象 * * @author robin * @date 2014年12月18日 */class Person implements Serializable { private static final long serialVersionUID = -6412852654889352693L; /** * 姓名 */ private String name; /** * 年齡 */ private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}關(guān)于對(duì)象序列化與反序列化還有幾點(diǎn)需要注意:
在一些特殊場(chǎng)景下,比如銀行賬戶(hù)對(duì)象,出于保密考慮,不希望對(duì)存款金額進(jìn)行序列化。或者類(lèi)的一些引用類(lèi)型的成員是不可序列化的。此時(shí)可以使用transient關(guān)鍵字修飾不想被或者不能被序列化的成員變量。
繼續(xù)調(diào)整我們的代碼來(lái)做演示:
package com.zhyea.test;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;/** * 序列化測(cè)試類(lèi) * * @author robin * @date 2014年12月18日 */public class SerialTest { public static void main(String[] args) { Person robin = new Person("robin", 29); School school = new School("XX學(xué)校"); Teacher tRobin = new Teacher(robin); tRobin.setSchool(school); tRobin.setSalary(12.0); String savePath = "D://object.txt"; SerialTest test = new SerialTest(); try { test.serialize(savePath, tRobin); Teacher t = (Teacher) test.deSerialize(savePath); System.out.println("Name:" + t.getPerson().getName() +" Salary:" + t.getSalary()); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 實(shí)現(xiàn)序列化 * * @param obj * 要被序列化保存的對(duì)象 * @param path * 保存地址 * @throws IOException */ public void serialize(String path, Object ... obj) throws IOException { .... } /** * 反序列化取出對(duì)象 * * @param path * 被序列化對(duì)象保存的位置 * @return * @throws IOException * @throws ClassNotFoundException */ public Object deSerialize(String path) throws IOException, ClassNotFoundException { ... }}/** * Teacher類(lèi) * @author robin * @date 2014年12月18日 */class Teacher implements Serializable{ private static final long serialVersionUID = -8751853088437904443L; private Person person; private transient School school; private transient double salary; public Teacher(Person person){ this.person = person; } /*略去get、set,請(qǐng)自行補(bǔ)充*/}/** * School類(lèi),不可序列化 * * @author robin * @date 2014年12月18日 */class School{ private String name; public School(String name){ this.name = name; } /*略去get、set,請(qǐng)自行補(bǔ)充*/}/** * Person類(lèi),可序列化 * * @author robin * @date 2014年12月18日 */class Person implements Serializable { ....}在不對(duì)Teacher類(lèi)的school成員添加transient標(biāo)識(shí)的情況下,若school值不為null,會(huì)報(bào)NotSerializableException。異常信息如下: 
在不對(duì)Teacher類(lèi)的salary成員添加transient標(biāo)識(shí)的時(shí)候,會(huì)如實(shí)輸出salary的值,添加后則只會(huì)輸出salary的默認(rèn)初始值即0.0。

需要注意的是transient只能修飾屬性(filed),不能修飾類(lèi)或方法。
自定義序列化transient提供了一種簡(jiǎn)潔的方式將被transient修飾的成員屬性完全隔離在序列化機(jī)制之外。這樣子固然不錯(cuò),但是Java還提供了一種自定義序列化機(jī)制讓開(kāi)發(fā)者更自由地控制如何序列化各個(gè)成員屬性,或者不序列化某些屬性(與transient效果相同)。
在需要自定義序列化和反序列化的類(lèi)中需要提供以下方法:
先說(shuō)下前兩個(gè)方法writeObject和readObject,這兩個(gè)方法和ObjectOutputStream及ObjectInputStream里對(duì)應(yīng)的方法名稱(chēng)相同。實(shí)際上,盡管這兩個(gè)方法是private型的,但是仍然是在被序列化(或反序列化)階段被外部類(lèi)ObjectOutputStream(或ObjectInputStream)調(diào)用。僅以序列化為例,ObjectOutputStream在執(zhí)行自己的writeObject方法前會(huì)先通過(guò)反射在要被序列化的對(duì)象的類(lèi)中(有點(diǎn)繞口是吧)查找有無(wú)自定義的writeObject方法,如有的話,則會(huì)優(yōu)先調(diào)用自定義的writeObject方法。因?yàn)椴檎曳瓷浞椒〞r(shí)使用的是getPrivateMethod,所以自定以的writeObject方法的作用域要被設(shè)置為private。通過(guò)自定義writeObject和readObject方法可以完全控制對(duì)象的序列化與反序列化。
如下是示例代碼:
package com.zhyea.test;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;import com.sun.xml.internal.ws.encoding.soap.DeserializationException;/** * 序列化測(cè)試類(lèi) * * @author robin * @date 2014年12月18日 */public class SerialTest { public static void main(String[] args) { Person robin = new Person("robin", 29); String savePath = "D://object.txt"; SerialTest test = new SerialTest(); try { test.serialize(savePath, robin); Person person = (Person) test.deSerialize(savePath); System.out.println("Name:" + person.getName() +" Age:" + person.getAge()); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 實(shí)現(xiàn)序列化 * * @param obj * 要被序列化保存的對(duì)象 * @param path * 保存地址 * @throws IOException */ public void serialize(String path, Person ... obj) throws IOException { ObjectOutputStream oos = null; ... } /** * 反序列化取出對(duì)象 * * @param path * 被序列化對(duì)象保存的位置 * @return * @throws IOException * @throws ClassNotFoundException */ public Object deSerialize(String path) throws IOException, ClassNotFoundException { ... }}/** * Person類(lèi),可序列化 * * @author robin * @date 2014年12月18日 */class Person implements Serializable { private static final long serialVersionUID = -6412852654889352693L; /** * 姓名 */ private String name; /** * 年齡 */ private int age; public Person() {} public Person(String name, int age) { this.name = name; this.age = age; } /* 略去get和set,請(qǐng)自行實(shí)現(xiàn) */ private void writeObject(ObjectOutputStream out) throws IOException{ out.writeObject(name); out.writeInt(age + 1); System.out.println("my write"); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{ this.name = "zhangsan"; this.age = 30; System.out.println("my read"); }}
以下是輸出結(jié)果:

關(guān)于readObjectNoData,在網(wǎng)上找了如下一段說(shuō)明:
readObjectNoData 原始情況 pojo public class Person implements Serializable { private int age; public Person() { } //setter getter... } 序列化 Person p = new Person(); p.setAge(10); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("c:/person.ser")); oos.writeObject(p); oos.flush(); oos.close(); 類(lèi)結(jié)構(gòu)變化后, 序列化數(shù)據(jù)不變 pojo Animal implements Serializable 顯式編寫(xiě)readObjectNoData public class Animal implements Serializable { private String name; public Animal() { } //setter getter... private void readObjectNoData() { this.name = "zhangsan"; } } Person extends Animal public class Person extends Animal implements Serializable { private int age; public Person() { } // setter getter... } 反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c:/person.ser")); Person sp = (Person) ois.readObject(); System.out.println(sp.getName()); readObject時(shí), 會(huì)調(diào)用readObjectNoDatareadObjectNoData在我理解看來(lái)像是一種異常處理機(jī)制,用來(lái)在序列化的流不完整的情況下返回正確的值。
使用 writeReplace和readResolvewriteReplace和readResolve是一種更徹底的序列化的機(jī)制,它甚至可以將序列化的目標(biāo)對(duì)象替換為其它的對(duì)象。
但是與writeObject和readObject不同的是,這二者不是必須要一起使用的,而且盡量應(yīng)分開(kāi)使用。若一起使用的話,只有writeReplace會(huì)生效。
代碼可以說(shuō)明一切,首先是writeReplace:
package com.zhyea.test;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.ObjectStreamException;import java.io.Serializable;/** * 序列化測(cè)試類(lèi) * * @author robin * @date 2014年12月18日 */public class SerialTest { public static void main(String[] args) { Person robin = new Person("robin", 29); String savePath = "D://object.txt"; SerialTest test = new SerialTest(); try { //序列化 test.serialize(savePath, robin); //反序列化 String person = (String) test.deSerialize(savePath); System.out.println(person); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 實(shí)現(xiàn)序列化 * * @param obj * 要被序列化保存的對(duì)象 * @param path * 保存地址 * @throws IOException */ public void serialize(String path, Person ... obj) throws IOException { ObjectOutputStream oos = null; .... } /** * 反序列化取出對(duì)象 * * @param path * 被序列化對(duì)象保存的位置 * @return * @throws IOException * @throws ClassNotFoundException */ public Object deSerialize(String path) throws IOException, ClassNotFoundException { .... }}/** * Person類(lèi),可序列化 * * @author robin * @date 2014年12月18日 */class Person implements Serializable { private static final long serialVersionUID = -6412852654889352693L; /** * 姓名 */ private String name; /** * 年齡 */ private int age; public Person() {} public Person(String name, int age) { this.name = name; this.age = age; } /* set和get方法請(qǐng)自行添加 */ private Object writeReplace() throws ObjectStreamException{ System.out.println("my writeReplace"); return "robin"; } private Object readResolve() throws ObjectStreamException{ System.out.println("my readResolve"); return "zhangsan"; } private void writeObject(ObjectOutputStream out) throws IOException{ .... } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{ .... }}以下是運(yùn)行結(jié)果:

在Person類(lèi)中,保留了之前的writeObject和readObject方法,并且還添加了readResolve方法。但是從運(yùn)行結(jié)果可以看出來(lái),這3個(gè)方法都沒(méi)有被調(diào)用,只有writeReplace方法被使用。可以理解為當(dāng)使用writeReplace時(shí),其他的自定義方法都不會(huì)被調(diào)用,即writeReplace的優(yōu)先級(jí)最高。
現(xiàn)在注釋掉writeReplace方法,再次執(zhí)行,結(jié)果如下:

這次writeObject,readObject和readResolve方法都被調(diào)用。readResolve方法緊跟著readObject方法被調(diào)用且最終返回的值是readResolve返回的值,readObject里反序列化生成的對(duì)象被拋棄。
此外還有一點(diǎn)需要說(shuō)明:writeReplace和readResolve可以使用任何作用域,這意味著子類(lèi)也可以調(diào)用超類(lèi)的這兩個(gè)方法。但是如果子類(lèi)還有不同的序列化及反序列化需求,這就需要子類(lèi)重寫(xiě)這個(gè)方法,有些時(shí)候這樣做是沒(méi)有必要的。因此一般情況下將這兩個(gè)方法的作用域設(shè)置為private。
使用Externalizable一開(kāi)始有提到過(guò)實(shí)現(xiàn)Externalizable接口也可以實(shí)現(xiàn)類(lèi)的序列化。使用這種方法,可以由開(kāi)發(fā)者完全決定如何序列化和反序列化目標(biāo)對(duì)象。Externalizable接口提供了writeExternal和readExternal兩個(gè)方法。
實(shí)際上這種方法和前面的自定義序列化方法很相似,只是Externalizable強(qiáng)制自定義序列化。在使用了Externalizable的類(lèi)中仍可以使用writeReplace和readResolve方法。使用Externalizable進(jìn)行序列化較之使用Serializable性能略好,但是復(fù)雜度較高。
版本問(wèn)題執(zhí)行序列化和反序列化時(shí)有可能會(huì)遇到JRE版本問(wèn)題。尤其是在網(wǎng)絡(luò)的兩端進(jìn)行通信時(shí),這種情況更為多見(jiàn)。
為了解決這種問(wèn)題,Java允許為序列化的類(lèi)提供一個(gè)serialVersionUID的常量標(biāo)識(shí)該類(lèi)的版本。只要serialVersionUID的值不變,Java就會(huì)把它們當(dāng)作相同的序列化版本。
如果不顯式定義serialVersionUID,那么JVM就會(huì)計(jì)算出一個(gè)serialVersionUID的值。不同的編譯器下會(huì)產(chǎn)生不同的serialVersionUID值。serialVersionUID值不同則會(huì)導(dǎo)致編譯失敗。可以使用jdk的bin目錄下的serial.exe查看可序列化類(lèi)的serialVersionUID,指令如下:
serial Person
如果對(duì)類(lèi)的修改確實(shí)會(huì)導(dǎo)致反序列化失敗,則應(yīng)主動(dòng)調(diào)整serialVersionUID的值。導(dǎo)致類(lèi)的反序列化失敗的修改有以下幾種情形:
關(guān)于對(duì)象的序列化,總結(jié)下注意事項(xiàng):
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注