深入剖析Java編程中的序列化
Java提供一種機制叫做序列化,通過有序的格式或者字節(jié)序列持久化java對象,其中包含對象的數(shù)據,還有對象的類型,和保存在對象中的數(shù)據類型。
所以,如果我們已經序列化了一個對象,那么它可以被讀取并通過對象的類型和其他信息進行反序列化,并最終獲取對象的原型。
ObjectInputStream 和 ObjectOutputStream對象是高級別的流對象,包含序列化和反序列化的方法。
ObjectOutputStream 擁有很多序列化對象的方法,最常用的是:
private void writeObject(ObjectOutputStream os) throws IOException
{
}
類似的 ObjectInputStream 提供如下方法:
private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException
{
}
那么哪里會需要序列化呢?序列化通常在需要通過網絡傳輸數(shù)據,或者保存對象到文件的場合使用。這里說的數(shù)據是對象而不是文本。
現(xiàn)在的問題是,我們的網絡架構和硬盤都只能識別二進制和字節(jié),而不能識別Java對象。
序列化就是把Java對象中的value/states翻譯為字節(jié),以便通過網絡傳輸或者保存。另外,反序列化就是通過讀取字節(jié)碼,并把它翻譯回java對象。
serialVersionUID概念
serialVersionUID 是用于保證同一個對象(在序列化中會被用到)可以在Deserialization過程中被載入。serialVersionUID 是用于對象的版本控制。你可以參考serialVersionUID in java serialization獲取更多信息。
對于序列化:
步驟如下:
讓我們看一個列子:
在 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;
}
}
就如你所見的,如果你需要序列化任何類,那么你 必須實現(xiàn) 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中,創(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.運行:
首先運行SerializeMain.java,然后運行 DeserializeMain.java,你會得到如下的結果:
Deserialized Employee... Emp id: 101 Name: Arpit Department: CS
就這樣,我們序列化了一個employee對象,并對它進行反序列化。這看起來和簡單,但是如果其中包含對象引用,繼承,那么情況就會變得復雜。接下來讓我們一個接一個的看一下例子,看看如何在各種場合中實現(xiàn)序列化。
案例1 - 如果對象引用了其他對象,那該如何
我們已經看過最簡單的序列化例子,現(xiàn)在看看,如何處理對象中引用了其他對象的場合。我們該如何序列化?引用對象也會被序列化嗎?對的,你不需要顯式的序列化引用對象。當你序列化任何對象,如果它包含引用對象,那么Java序列化會自動序列化該對象的整個對象圖。例如,Employee現(xiàn)在引用了一個address對象,并且Address也引用了其他對象(例如,Home),那么當你序列化Employee對象的時候,所有其他引用對象,例如address和home將會被自動地被序列化。讓我們來創(chuàng)建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 包中,創(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());
}
}
運行它:
當你運行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: 101 Name: Arpit Department: CS City :Pune
案例2:如果我們不能訪問引用對象的源代碼(例如,你不能訪問上面的Address類的源碼)
如果我們不能訪問到address類,那么我們該如何在Address類中實現(xiàn)serializable 接口?是否有另外的途徑來實現(xiàn)呢?對的,你可以創(chuàng)建另外一個類,并繼承Address,然后讓它繼承serializable 接口,但是對于下面的情況,這個方案會失?。?/p>
如果引用類被定義為final
如果引用類引用了另外一個非可序列化的對象
那么,我們該如何序列化Employee對象?解決的辦法是,標記transient。如果你不需要序列化任何字段,只需把它標記為transient。
transient Address address
在Employee類中,標記了address為transient之后,運行程序。你會得到nullPointerException,因為在反序列化過程中,Address引用將會是null。
案例3 - 如果我仍然需要保存引用對象的狀態(tài)呢?(例如address對象)
如果你在反序列化過程中,標記了address為transient,它將會返回null結果。但是如果你仍然需要保存它的狀態(tài),你就需要序列化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讀取數(shù)據的順序和ObjectOutputStream寫入數(shù)據的順序是一致的.
在包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());
}
}
運行 :
當你運行SerializeDeserializeMain.java.你會得到如下的結果:
Deserialized Employee... Emp id: 101 Name: Arpit Department: CS City :Pune
現(xiàn)在我們就得到了address對象的狀態(tài),就如它被序列化前的一樣。
序列化中的繼承:
現(xiàn)在我們看看繼承是如何影響序列化的。不管父類是不是可序列化,這將引出很多個例子。如果父類是非可序列化的,我們將如何處理,并且它是如何工作的。讓我們看看例子。
我們將創(chuàng)建一個Person.java,作為 Employee的父類。
案例4: 如果父類是可序列化的
如果父類可序列化,那么所有的繼承類將是可序列化的。
案例5: 如果父類為非可序列化呢?
如果父類為非可序列化的 ,那么我們的處理辦法會很不一樣。
如果父類為非可序列化的,那么它必然不會有參數(shù)構造函數(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());
}
}
運行:
當你運行SerializeDeserializeMain.java后,你會得到如下的輸出,如果父類是非可序列化的,那么在反序列化過程中,所有繼承于父類的實例變量值,將會通過調用非序列化構造函數(shù)來初始化。 這里 name繼承于person,所以在反序列化過程中,name將會被初始化為默認值。
案例6 - 如果父類是可序列化,但你不需要繼承類為可序列化
如果你不希望繼承類為可序列化,那么你需要實現(xiàn) writeObject() 和readObject() 方法,并且需要拋出NotSerializableException 異常。
案例7 - 可否序列化靜態(tài)變量?
不能。因為靜態(tài)變量是類級別的,不是對象級別的,當你序列化一個對象的時候,是不能序列化靜態(tài)變量。
總結:
- 序列化是java對象的values/states轉化為字節(jié)并在網絡中傳輸或者保存它的過程。另外反序列化是把字節(jié)碼翻譯為對應的對象的過程。
- 序列化的好處是,JVM的獨立性,也就是說,一個對象可以在一個平臺中被序列化,然后在另外一個不同的平臺反序列化。
- 如果你需要序列化任何對象,你必須實現(xiàn)標記接口Serializable。
- Java中的標記接口(Marker interface)就是沒有字段或者方法的接口,或者更簡單的說,空接口
- serialVersionUID 是用于保證同一個對象(在序列化中會被用到)可以在Deserialization過程中被載入。serialVersionUID 是用于對象的版本控制。
- 當你需要序列化任何包含引用對象的對象,那么Java會自動序列化該對象的整個對象圖。
- 如果你不希望序列化某個字段,你可以標記它為trasient
- 如果父類為可序列化,那么它的繼承類也將是可序列化的。
- 如果父類為非可序列化,那么在反序列化過程中,所有繼承于父類的實例變量值將會通過調用非可序列化的構造器來初始化。
- 如果你需希望子類為可序列化的,那么你需要實現(xiàn)writeObject() 和 readObject() 方法,并在這兩個方法中拋出NotSerializableException異常
- 你不能序列化靜態(tài)變量。
相關文章
springboot中shiro使用自定義注解屏蔽接口鑒權實現(xiàn)
本文主要介紹了springboot中shiro使用自定義注解屏蔽接口鑒權實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-07-07
Java 執(zhí)行CMD命令或執(zhí)行BAT批處理方式
這篇文章主要介紹了Java 執(zhí)行CMD命令或執(zhí)行BAT批處理方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
IDEA2020導入非maven項目并部署tomcat的方法
這篇文章主要介紹了IDEA 2020 導入非maven項目并部署tomcat的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-07-07

