Java提供一種機制叫做序列化,通過有序的格式或者字節序列持久化java對象,其中包含對象的數據,還有對象的類型,和保存在對像中的數據類型。
所以,如果我們已經序列化了一個對象,那麼它可以被讀取並通過對象的類型和其他信息進行反序列化,並最終獲取對象的原型。
ObjectInputStream 和ObjectOutputStream對像是高級別的流對象,包含序列化和反序列化的方法。
ObjectOutputStream 擁有很多序列化對象的方法,最常用的是:
private void writeObject(ObjectOutputStream os) throws IOException { }
類似的ObjectInputStream 提供如下方法:
private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException { }
那麼哪裡會需要序列化呢?序列化通常在需要通過網絡傳輸數據,或者保存對像到文件的場合使用。這裡說的數據是對象而不是文本。
現在的問題是,我們的網絡架構和硬盤都只能識別二進制和字節,而不能識別Java對象。
序列化就是把Java對像中的value/states翻譯為字節,以便通過網絡傳輸或者保存。另外,反序列化就是通過讀取字節碼,並把它翻譯回java對象。
serialVersionUID概念
serialVersionUID 是用於保證同一個對象(在序列化中會被用到)可以在Deserialization過程中被載入。 serialVersionUID 是用於對象的版本控制。你可以參考serialVersionUID in java serialization獲取更多信息。
對於序列化:
步驟如下:
讓我們看一個列子:
在src->org.arpit.javapostsforlearning 創建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; }}
就如你所見的,如果你需要序列化任何類,那麼你必須實現Serializable 接口,這個接口是標記接口(marker interface)。
Java中的標記接口(marker interface)就是一個沒有任何字段或者方法的接口,簡單的來說,java中把空接口叫做標記接口(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(); } }}
對於反序列化:
步驟是
在包src->org.arpit.javapostsforlearning中,創建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.運行:
首先運行SerializeMain.java,然後運行DeserializeMain.java,你會得到如下的結果:
Deserialized Employee...Emp id: 101Name: ArpitDepartment: CS
就這樣,我們序列化了一個employee對象,並對它進行反序列化。這看起來和簡單,但是如果其中包含對象引用,繼承,那麼情況就會變得複雜。接下來讓我們一個接一個的看一下例子,看看如何在各種場合中實現序列化。
案例1 - 如果對象引用了其他對象,那該如何
我們已經看過最簡單的序列化例子,現在看看,如何處理對像中引用了其他對象的場合。我們該如何序列化?引用對像也會被序列化嗎?對的,你不需要顯式的序列化引用對象。當你序列化任何對象,如果它包含引用對象,那麼Java序列化會自動序列化該對象的整個對像圖。例如,Employee現在引用了一個address對象,並且Address也引用了其他對象(例如,Home),那麼當你序列化Employee對象的時候,所有其他引用對象,例如address和home將會被自動地被序列化。讓我們來創建Address類,並它Address的對像作為引用,添加到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 包中,創建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中,創建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()); }}
運行它:
當你運行SerializeDeserializeMain.java。你會得到這樣的結果:
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)
我們將解釋哪裡出錯了。我忘記了說,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; }}
再次運行:
當你再次運行SerializeDeserializeMain.java。你可以得到如下的結果
Deserialized Employee...Emp id: 101Name: ArpitDepartment: CSCity :Pune
案例2:如果我們不能訪問引用對象的源代碼(例如,你不能訪問上面的Address類的源碼)
如果我們不能訪問到address類,那麼我們該如何在Address類中實現serializable 接口?是否有另外的途徑來實現呢?對的,你可以創建另外一個類,並繼承Address,然後讓它繼承serializable 接口,但是對於下面的情況,這個方案會失敗:
如果引用類被定義為final
如果引用類引用了另外一個非可序列化的對象
那麼,我們該如何序列化Employee對象?解決的辦法是,標記transient。如果你不需要序列化任何字段,只需把它標記為transient。
transient Address address
在Employee類中,標記了address為transient之後,運行程序。你會得到nullPointerException,因為在反序列化過程中,Address引用將會是null。
案例3 - 如果我仍然需要保存引用對象的狀態呢? (例如address對象)
如果你在反序列化過程中,標記了address為transient,它將會返回null結果。但是如果你仍然需要保存它的狀態,你就需要序列化address對象。 Java序列化提供一個機制,如果你有特定簽名的private方法,那麼它們就會在序列化和反序列化過程中被調用,所以我們將重寫Employee類的writeObject和readObject方法,然後它們就會在Employee對象序列化/反序列化過程中被調用。
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(); } }}
另外有一點需要牢記的,ObjectInputStream讀取數據的順序和ObjectOutputStream寫入數據的順序是一致的.
在包org.arpit.javapostsforlearning 中創建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中創建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()); }}
運行:
當你運行SerializeDeserializeMain.java.你會得到如下的結果:
Deserialized Employee...Emp id: 101Name: ArpitDepartment: CSCity :Pune
現在我們就得到了address對象的狀態,就如它被序列化前的一樣。
序列化中的繼承:
現在我們看看繼承是如何影響序列化的。不管父類是不是可序列化,這將引出很多個例子。如果父類是非可序列化的,我們將如何處理,並且它是如何工作的。讓我們看看例子。
我們將創建一個Person.java,作為Employee的父類。
案例4: 如果父類是可序列化的
如果父類可序列化,那麼所有的繼承類將是可序列化的。
案例5: 如果父類為非可序列化呢?
如果父類為非可序列化的,那麼我們的處理辦法會很不一樣。
如果父類為非可序列化的,那麼它必然不會有參數構造函數。
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 中創建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包中創建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()); }}
運行:
當你運行SerializeDeserializeMain.java後,你會得到如下的輸出,如果父類是非可序列化的,那麼在反序列化過程中,所有繼承於父類的實例變量值,將會通過調用非序列化構造函數來初始化。 這裡name繼承於person,所以在反序列化過程中,name將會被初始化為默認值。
案例6 - 如果父類是可序列化,但你不需要繼承類為可序列化
如果你不希望繼承類為可序列化,那麼你需要實現writeObject() 和readObject() 方法,並且需要拋出NotSerializableException 異常。
案例7 - 可否序列化靜態變量?
不能。因為靜態變量是類級別的,不是對象級別的,當你序列化一個對象的時候,是不能序列化靜態變量。
總結: