深入剖析Java編程中的序列化
Java提供一種機(jī)制叫做序列化,通過有序的格式或者字節(jié)序列持久化java對(duì)象,其中包含對(duì)象的數(shù)據(jù),還有對(duì)象的類型,和保存在對(duì)象中的數(shù)據(jù)類型。
所以,如果我們已經(jīng)序列化了一個(gè)對(duì)象,那么它可以被讀取并通過對(duì)象的類型和其他信息進(jìn)行反序列化,并最終獲取對(duì)象的原型。
ObjectInputStream 和 ObjectOutputStream對(duì)象是高級(jí)別的流對(duì)象,包含序列化和反序列化的方法。
ObjectOutputStream 擁有很多序列化對(duì)象的方法,最常用的是:
private void writeObject(ObjectOutputStream os) throws IOException { }
類似的 ObjectInputStream 提供如下方法:
private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException { }
那么哪里會(huì)需要序列化呢?序列化通常在需要通過網(wǎng)絡(luò)傳輸數(shù)據(jù),或者保存對(duì)象到文件的場合使用。這里說的數(shù)據(jù)是對(duì)象而不是文本。
現(xiàn)在的問題是,我們的網(wǎng)絡(luò)架構(gòu)和硬盤都只能識(shí)別二進(jìn)制和字節(jié),而不能識(shí)別Java對(duì)象。
序列化就是把Java對(duì)象中的value/states翻譯為字節(jié),以便通過網(wǎng)絡(luò)傳輸或者保存。另外,反序列化就是通過讀取字節(jié)碼,并把它翻譯回java對(duì)象。
serialVersionUID概念
serialVersionUID 是用于保證同一個(gè)對(duì)象(在序列化中會(huì)被用到)可以在Deserialization過程中被載入。serialVersionUID 是用于對(duì)象的版本控制。你可以參考serialVersionUID in java serialization獲取更多信息。
對(duì)于序列化:
步驟如下:
讓我們看一個(gè)列子:
在 src->org.arpit.javapostsforlearning 創(chuàng)建Employee.java
1.Employee.java
package org.arpit.javapostsforlearning; import java.io.Serializable; public class Employee implements Serializable{ int employeeId; String employeeName; String department; public int getEmployeeId() { return employeeId; } public void setEmployeeId(int employeeId) { this.employeeId = employeeId; } public String getEmployeeName() { return employeeName; } public void setEmployeeName(String employeeName) { this.employeeName = employeeName; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } }
就如你所見的,如果你需要序列化任何類,那么你 必須實(shí)現(xiàn) Serializable 接口 ,這個(gè)接口是標(biāo)記接口(marker interface)。
Java中的標(biāo)記接口(marker interface)就是一個(gè)沒有任何字段或者方法的接口,簡單的來說,java中把空接口叫做標(biāo)記接口(marker interface)
2.SerializeMain.java
package org.arpit.javapostsforlearning; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class SerializeMain { /** * @author Arpit Mandliya */ public static void main(String[] args) { Employee emp = new Employee(); emp.setEmployeeId(101); emp.setEmployeeName("Arpit"); emp.setDepartment("CS"); try { FileOutputStream fileOut = new FileOutputStream("employee.ser"); ObjectOutputStream outStream = new ObjectOutputStream(fileOut); outStream.writeObject(emp); outStream.close(); fileOut.close(); }catch(IOException i) { i.printStackTrace(); } } }
對(duì)于反序列化:
步驟是
在包src->org.arpit.javapostsforlearning中,創(chuàng)建 DeserializeMain.java
3.DeserializeMain.java
package org.arpit.javapostsforlearning; import java.io.IOException; import java.io.ObjectInputStream; public class DeserializeMain { /** * @author Arpit Mandliya */ public static void main(String[] args) { Employee emp = null; try { FileInputStream fileIn =new FileInputStream("employee.ser"); ObjectInputStream in = new ObjectInputStream(fileIn); emp = (Employee) in.readObject(); in.close(); fileIn.close(); }catch(IOException i) { i.printStackTrace(); return; }catch(ClassNotFoundException c) { System.out.println("Employee class not found"); c.printStackTrace(); return; } System.out.println("Deserialized Employee..."); System.out.println("Emp id: " + emp.getEmployeeId()); System.out.println("Name: " + emp.getEmployeeName()); System.out.println("Department: " + emp.getDepartment()); } }
4.運(yùn)行:
首先運(yùn)行SerializeMain.java,然后運(yùn)行 DeserializeMain.java,你會(huì)得到如下的結(jié)果:
Deserialized Employee... Emp id: 101 Name: Arpit Department: CS
就這樣,我們序列化了一個(gè)employee對(duì)象,并對(duì)它進(jìn)行反序列化。這看起來和簡單,但是如果其中包含對(duì)象引用,繼承,那么情況就會(huì)變得復(fù)雜。接下來讓我們一個(gè)接一個(gè)的看一下例子,看看如何在各種場合中實(shí)現(xiàn)序列化。
案例1 - 如果對(duì)象引用了其他對(duì)象,那該如何
我們已經(jīng)看過最簡單的序列化例子,現(xiàn)在看看,如何處理對(duì)象中引用了其他對(duì)象的場合。我們?cè)撊绾涡蛄谢??引用?duì)象也會(huì)被序列化嗎?對(duì)的,你不需要顯式的序列化引用對(duì)象。當(dāng)你序列化任何對(duì)象,如果它包含引用對(duì)象,那么Java序列化會(huì)自動(dòng)序列化該對(duì)象的整個(gè)對(duì)象圖。例如,Employee現(xiàn)在引用了一個(gè)address對(duì)象,并且Address也引用了其他對(duì)象(例如,Home),那么當(dāng)你序列化Employee對(duì)象的時(shí)候,所有其他引用對(duì)象,例如address和home將會(huì)被自動(dòng)地被序列化。讓我們來創(chuàng)建Address類,并它Address的對(duì)象作為引用,添加到employee類中。
Employee.java:
package org.arpit.javapostsforlearning; import java.io.Serializable; public class Employee implements Serializable{ int employeeId; String employeeName; String department; Address address; public int getEmployeeId() { return employeeId; } public void setEmployeeId(int employeeId) { this.employeeId = employeeId; } public String getEmployeeName() { return employeeName; } public void setEmployeeName(String employeeName) { this.employeeName = employeeName; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } }
在 org.arpit.javapostsforlearning 包中,創(chuàng)建Address.java
Address.java:
package org.arpit.javapostsforlearning; public class Address { int homeNo; String street; String city; public Address(int homeNo, String street, String city) { super(); this.homeNo = homeNo; this.street = street; this.city = city; } public int getHomeNo() { return homeNo; } public void setHomeNo(int homeNo) { this.homeNo = homeNo; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } }
在包 org.arpit.javapostsforlearning中,創(chuàng)建SerializeDeserializeMain.java
SerializeDeserializeMain.java:
package org.arpit.javapostsforlearning; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializeDeserializeMain { /** * @author Arpit Mandliya */ public static void main(String[] args) { Employee emp = new Employee(); emp.setEmployeeId(101); emp.setEmployeeName("Arpit"); emp.setDepartment("CS"); Address address=new Address(88,"MG road","Pune"); emp.setAddress(address); //Serialize try { FileOutputStream fileOut = new FileOutputStream("employee.ser"); ObjectOutputStream outStream = new ObjectOutputStream(fileOut); outStream.writeObject(emp); outStream.close(); fileOut.close(); }catch(IOException i) { i.printStackTrace(); } //Deserialize emp = null; try { FileInputStream fileIn =new FileInputStream("employee.ser"); ObjectInputStream in = new ObjectInputStream(fileIn); emp = (Employee) in.readObject(); in.close(); fileIn.close(); }catch(IOException i) { i.printStackTrace(); return; }catch(ClassNotFoundException c) { System.out.println("Employee class not found"); c.printStackTrace(); return; } System.out.println("Deserialized Employee..."); System.out.println("Emp id: " + emp.getEmployeeId()); System.out.println("Name: " + emp.getEmployeeName()); System.out.println("Department: " + emp.getDepartment()); address=emp.getAddress(); System.out.println("City :"+address.getCity()); } }
運(yùn)行它:
當(dāng)你運(yùn)行SerializeDeserializeMain.java。你會(huì)得到這樣的結(jié)果:
java.io.NotSerializableException: org.arpit.javapostsforlearning.Address at java.io.ObjectOutputStream.writeObject0(Unknown Source) at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source) at java.io.ObjectOutputStream.writeSerialData(Unknown Source) at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source) at java.io.ObjectOutputStream.writeObject0(Unknown Source) at java.io.ObjectOutputStream.writeObject(Unknown Source)
我們將解釋哪里出錯(cuò)了。我忘記了說,Address 類也必須是serializable。那么Address類必須繼承serialzable接口。
Address.java:
import java.io.Serializable; public class Address implements Serializable{ int homeNo; String street; String city; public Address(int homeNo, String street, String city) { super(); this.homeNo = homeNo; this.street = street; this.city = city; } public int getHomeNo() { return homeNo; } public void setHomeNo(int homeNo) { this.homeNo = homeNo; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } }
再次運(yùn)行:
當(dāng)你再次運(yùn)行SerializeDeserializeMain.java。你可以得到如下的結(jié)果
Deserialized Employee... Emp id: 101 Name: Arpit Department: CS City :Pune
案例2:如果我們不能訪問引用對(duì)象的源代碼(例如,你不能訪問上面的Address類的源碼)
如果我們不能訪問到address類,那么我們?cè)撊绾卧贏ddress類中實(shí)現(xiàn)serializable 接口?是否有另外的途徑來實(shí)現(xiàn)呢?對(duì)的,你可以創(chuàng)建另外一個(gè)類,并繼承Address,然后讓它繼承serializable 接口,但是對(duì)于下面的情況,這個(gè)方案會(huì)失?。?/p>
如果引用類被定義為final
如果引用類引用了另外一個(gè)非可序列化的對(duì)象
那么,我們?cè)撊绾涡蛄谢疎mployee對(duì)象?解決的辦法是,標(biāo)記transient。如果你不需要序列化任何字段,只需把它標(biāo)記為transient。
transient Address address
在Employee類中,標(biāo)記了address為transient之后,運(yùn)行程序。你會(huì)得到nullPointerException,因?yàn)樵诜葱蛄谢^程中,Address引用將會(huì)是null。
案例3 - 如果我仍然需要保存引用對(duì)象的狀態(tài)呢?(例如address對(duì)象)
如果你在反序列化過程中,標(biāo)記了address為transient,它將會(huì)返回null結(jié)果。但是如果你仍然需要保存它的狀態(tài),你就需要序列化address對(duì)象。 Java序列化提供一個(gè)機(jī)制,如果你有特定簽名的private方法,那么它們就會(huì)在序列化和反序列化過程中被調(diào)用,所以我們將重寫Employee類的writeObject和readObject方法,然后它們就會(huì)在Employee對(duì)象序列化/反序列化過程中被調(diào)用。
Employee.java:
package org.arpit.javapostsforlearning; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class Employee implements Serializable{ int employeeId; String employeeName; String department; transient Address address; public int getEmployeeId() { return employeeId; } public void setEmployeeId(int employeeId) { this.employeeId = employeeId; } public String getEmployeeName() { return employeeName; } public void setEmployeeName(String employeeName) { this.employeeName = employeeName; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } private void writeObject(ObjectOutputStream os) throws IOException, ClassNotFoundException { try { os.defaultWriteObject(); os.writeInt(address.getHomeNo()); os.writeObject(address.getStreet()); os.writeObject(address.getCity()); } catch (Exception e) { e.printStackTrace(); } } private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException { try { is.defaultReadObject(); int homeNo=is.readInt(); String street=(String) is.readObject(); String city=(String) is.readObject(); address=new Address(homeNo,street,city); } catch (Exception e) { e.printStackTrace(); } } }
另外有一點(diǎn)需要牢記的,ObjectInputStream讀取數(shù)據(jù)的順序和ObjectOutputStream寫入數(shù)據(jù)的順序是一致的.
在包org.arpit.javapostsforlearning 中創(chuàng)建Address.java
Address.java:
package org.arpit.javapostsforlearning; import java.io.Serializable; public class Address { int homeNo; String street; String city; public Address(int homeNo, String street, String city) { super(); this.homeNo = homeNo; this.street = street; this.city = city; } public int getHomeNo() { return homeNo; } public void setHomeNo(int homeNo) { this.homeNo = homeNo; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } }
在包org.arpit.javapostsforlearning中創(chuàng)建SerializeDeserializeMain.java
SerializeDeserializeMain.java:
package org.arpit.javapostsforlearning; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializeDeserializeMain { /** * @author Arpit Mandliya */ public static void main(String[] args) { Employee emp = new Employee(); emp.setEmployeeId(101); emp.setEmployeeName("Arpit"); emp.setDepartment("CS"); Address address=new Address(88,"MG road","Pune"); emp.setAddress(address); //Serialize try { FileOutputStream fileOut = new FileOutputStream("employee.ser"); ObjectOutputStream outStream = new ObjectOutputStream(fileOut); outStream.writeObject(emp); outStream.close(); fileOut.close(); }catch(IOException i) { i.printStackTrace(); } //Deserialize emp = null; try { FileInputStream fileIn =new FileInputStream("employee.ser"); ObjectInputStream in = new ObjectInputStream(fileIn); emp = (Employee) in.readObject(); in.close(); fileIn.close(); }catch(IOException i) { i.printStackTrace(); return; }catch(ClassNotFoundException c) { System.out.println("Employee class not found"); c.printStackTrace(); return; } System.out.println("Deserialized Employee..."); System.out.println("Emp id: " + emp.getEmployeeId()); System.out.println("Name: " + emp.getEmployeeName()); System.out.println("Department: " + emp.getDepartment()); address=emp.getAddress(); System.out.println("City :"+address.getCity()); } }
運(yùn)行 :
當(dāng)你運(yùn)行SerializeDeserializeMain.java.你會(huì)得到如下的結(jié)果:
Deserialized Employee... Emp id: 101 Name: Arpit Department: CS City :Pune
現(xiàn)在我們就得到了address對(duì)象的狀態(tài),就如它被序列化前的一樣。
序列化中的繼承:
現(xiàn)在我們看看繼承是如何影響序列化的。不管父類是不是可序列化,這將引出很多個(gè)例子。如果父類是非可序列化的,我們將如何處理,并且它是如何工作的。讓我們看看例子。
我們將創(chuàng)建一個(gè)Person.java,作為 Employee的父類。
案例4: 如果父類是可序列化的
如果父類可序列化,那么所有的繼承類將是可序列化的。
案例5: 如果父類為非可序列化呢?
如果父類為非可序列化的 ,那么我們的處理辦法會(huì)很不一樣。
如果父類為非可序列化的,那么它必然不會(huì)有參數(shù)構(gòu)造函數(shù)。
Person.java
package org.arpit.javapostsforlearning; public class Person { String name="default"; String nationality; public Person() { System.out.println("Person:Constructor"); } public Person(String name, String nationality) { super(); this.name = name; this.nationality = nationality; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNationality() { return nationality; } public void setNationality(String nationality) { this.nationality = nationality; } }
在包org.arpit.javapostsforlearning 中創(chuàng)建Employee.java
Employee.java:
package org.arpit.javapostsforlearning; import java.io.Serializable; public class Employee extends Person implements Serializable{ int employeeId; String department; public Employee(int employeeId,String name,String department,String nationality) { super(name,nationality); this.employeeId=employeeId; this.department=department; System.out.println("Employee:Constructor"); } public int getEmployeeId() { return employeeId; } public void setEmployeeId(int employeeId) { this.employeeId = employeeId; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } }
在org.arpit.javapostsforlearning包中創(chuàng)建SerializeDeserializeMain.java
SerializeDeserializeMain.java:
package org.arpit.javapostsforlearning; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializeDeserializeMain { /** * @author Arpit Mandliya */ public static void main(String[] args) { //Serialize Employee emp = new Employee(101,"Arpit","CS","Indian"); System.out.println("Before serializing"); System.out.println("Emp id: " + emp.getEmployeeId()); System.out.println("Name: " + emp.getName()); System.out.println("Department: " + emp.getDepartment()); System.out.println("Nationality: " + emp.getNationality()); System.out.println("************"); System.out.println("Serializing"); try { FileOutputStream fileOut = new FileOutputStream("employee.ser"); ObjectOutputStream outStream = new ObjectOutputStream(fileOut); outStream.writeObject(emp); outStream.close(); fileOut.close(); }catch(IOException i) { i.printStackTrace(); } //Deserialize System.out.println("************"); System.out.println("Deserializing"); emp = null; try { FileInputStream fileIn =new FileInputStream("employee.ser"); ObjectInputStream in = new ObjectInputStream(fileIn); emp = (Employee) in.readObject(); in.close(); fileIn.close(); }catch(IOException i) { i.printStackTrace(); return; }catch(ClassNotFoundException c) { System.out.println("Employee class not found"); c.printStackTrace(); return; } System.out.println("After serializing"); System.out.println("Emp id: " + emp.getEmployeeId()); System.out.println("Name: " + emp.getName()); System.out.println("Department: " + emp.getDepartment()); System.out.println("Nationality: " + emp.getNationality()); } }
運(yùn)行:
當(dāng)你運(yùn)行SerializeDeserializeMain.java后,你會(huì)得到如下的輸出,如果父類是非可序列化的,那么在反序列化過程中,所有繼承于父類的實(shí)例變量值,將會(huì)通過調(diào)用非序列化構(gòu)造函數(shù)來初始化。 這里 name繼承于person,所以在反序列化過程中,name將會(huì)被初始化為默認(rèn)值。
案例6 - 如果父類是可序列化,但你不需要繼承類為可序列化
如果你不希望繼承類為可序列化,那么你需要實(shí)現(xiàn) writeObject() 和readObject() 方法,并且需要拋出NotSerializableException 異常。
案例7 - 可否序列化靜態(tài)變量?
不能。因?yàn)殪o態(tài)變量是類級(jí)別的,不是對(duì)象級(jí)別的,當(dāng)你序列化一個(gè)對(duì)象的時(shí)候,是不能序列化靜態(tài)變量。
總結(jié):
- 序列化是java對(duì)象的values/states轉(zhuǎn)化為字節(jié)并在網(wǎng)絡(luò)中傳輸或者保存它的過程。另外反序列化是把字節(jié)碼翻譯為對(duì)應(yīng)的對(duì)象的過程。
- 序列化的好處是,JVM的獨(dú)立性,也就是說,一個(gè)對(duì)象可以在一個(gè)平臺(tái)中被序列化,然后在另外一個(gè)不同的平臺(tái)反序列化。
- 如果你需要序列化任何對(duì)象,你必須實(shí)現(xiàn)標(biāo)記接口Serializable。
- Java中的標(biāo)記接口(Marker interface)就是沒有字段或者方法的接口,或者更簡單的說,空接口
- serialVersionUID 是用于保證同一個(gè)對(duì)象(在序列化中會(huì)被用到)可以在Deserialization過程中被載入。serialVersionUID 是用于對(duì)象的版本控制。
- 當(dāng)你需要序列化任何包含引用對(duì)象的對(duì)象,那么Java會(huì)自動(dòng)序列化該對(duì)象的整個(gè)對(duì)象圖。
- 如果你不希望序列化某個(gè)字段,你可以標(biāo)記它為trasient
- 如果父類為可序列化,那么它的繼承類也將是可序列化的。
- 如果父類為非可序列化,那么在反序列化過程中,所有繼承于父類的實(shí)例變量值將會(huì)通過調(diào)用非可序列化的構(gòu)造器來初始化。
- 如果你需希望子類為可序列化的,那么你需要實(shí)現(xiàn)writeObject() 和 readObject() 方法,并在這兩個(gè)方法中拋出NotSerializableException異常
- 你不能序列化靜態(tài)變量。
您可能感興趣的文章
- 01-10Java咖啡館(1)——嘆咖啡
- 01-10Java Socket編程(三) 服務(wù)器Sockets
- 01-10Java進(jìn)階:Struts多模塊的技巧
- 01-10Java Socket編程(一) Socket傳輸模式
- 01-10Java Socket編程(二) Java面向連接的類
- 01-10Java運(yùn)行時(shí)多態(tài)性的實(shí)現(xiàn)
- 01-10Java經(jīng)驗(yàn)點(diǎn)滴:處理沒有被捕獲的異常
- 01-10Java Socket編程(四) 重復(fù)和并發(fā)服務(wù)器
- 01-10Java中的浮點(diǎn)數(shù)分析
- 01-10面向?qū)ο缶幊?Java中的抽象數(shù)據(jù)類型


閱讀排行
本欄相關(guān)
- 01-10Java咖啡館(1)——嘆咖啡
- 01-10JVM的垃圾回收機(jī)制詳解和調(diào)優(yōu)
- 01-10Java Socket編程(三) 服務(wù)器Sockets
- 01-10Java進(jìn)階:Struts多模塊的技巧
- 01-10J2SE 1.5版本的新特性一覽
- 01-10Java Socket編程(一) Socket傳輸模式
- 01-10Java運(yùn)行時(shí)多態(tài)性的實(shí)現(xiàn)
- 01-10Java Socket編程(二) Java面向連接的類
- 01-10Java Socket編程(四) 重復(fù)和并發(fā)服務(wù)
- 01-10Java經(jīng)驗(yàn)點(diǎn)滴:處理沒有被捕獲的異常
隨機(jī)閱讀
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 04-02jquery與jsp,用jquery
- 01-11ajax實(shí)現(xiàn)頁面的局部加載
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-10C#中split用法實(shí)例總結(jié)
- 01-10delphi制作wav文件的方法
- 08-05織夢dedecms什么時(shí)候用欄目交叉功能?
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 01-10使用C語言求解撲克牌的順子及n個(gè)骰子
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改