Java序列化總結(jié)
序列化就是指將Java對象轉(zhuǎn)換成一系列的字節(jié)秒裕,反序列化即使將一系列的字節(jié)恢復成Java對象。序列話的主要用途在于:
- 持久化對象 - 可以將對象輸出的磁盤等介質(zhì)上
- 跨進程調(diào)用 - 不同進程無法相互訪問對方的地址空間丹皱,可以通過序列化+IPC在不同進程間共享對象
- 通過網(wǎng)絡(luò)傳輸對象 - 將對象序列化后通過網(wǎng)絡(luò)傳輸,以實現(xiàn)客戶端和服務(wù)器之間共享對象
Serializable接口的使用
Java提供了Serialzable接口用于序列化,Serilizable接口是一個標記接口产上,實現(xiàn)這個對象的類無需事先任何方法撑蒜。
ObjectOutputStream & ObjectIntputStream
ObjectOutputStream 和 ObjectIntputStream 分別用于將對象作為字節(jié)流輸出以及將字節(jié)流轉(zhuǎn)換成對象歹啼。下面這例子有兩個java程序,一個用于寫每一個用于讀:
- 測試類
package com.minghui.test;
import java.io.Serializable;
class TestClass implements Serializable {
public static final long serialVersionUID = 1L;
public static String sStaticField = "static field";
public String mName = "TestClass";
}
讀程序和寫程序分別都需要包辦這個類座菠。
- 寫程序
public class SerilizationWriteTest {
public static void main(String[] args) {
TestClass myTest = new TestClass();
myTest.mName = "New Name";
TestClass.sStaticField = "New Static Value";
ObjectOutputStream output = null;
try {
output = new ObjectOutputStream(new FileOutputStream("D:/result"));
output.writeObject(myTest);
output.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (output != null) {
try {
output.close();
} catch (IOException e) {
}
}
}
}
}
- 讀程序
public class SerilazationReadTest {
public static void main(String[] args) {
ObjectInputStream input = null;
try {
input = new ObjectInputStream(new FileInputStream("D:/result"));
TestClass test = (TestClass) input.readObject();
System.out.println("SerilazationREAD name:" + test.mName);
System.out.println("SerilazationREAD sStaticField:" + TestClass.sStaticField);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
讀程序輸出結(jié)果為:
SerilazationREAD name:New Name
SerilazationREAD sStaticField:static field
需要注意的一點是狸眼,靜態(tài)變量是不參與序列化的,在上面的例子中寫程序?qū)o態(tài)變量的復制并不會影響讀程序中靜態(tài)變量的值浴滴。
類的成員的序列化
如果類中的某些成員無法被序列化會出現(xiàn)什么情況呢?這里為TestClass添加一個未繼承Serilizable的成員:
OtherClass mOtherField = new OtherClass();
public class OtherClass {
public int x = 0;
public int y = 0;
OtherClass() {
x = 2;
y = 2;
}
}
修改寫程序:
public static void main(String[] args) {
TestClass myTest = new TestClass();
myTest.mName = "New Name";
myTest.mOtherField.x = 1;
myTest.mOtherField.y = 1;
TestClass.sStaticField = "New Static Value";
ObjectOutputStream output = null;
try {
output = new ObjectOutputStream(new FileOutputStream("D:/result"));
output.writeObject(myTest);
output.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (output != null) {
try {
output.close();
} catch (IOException e) {
}
}
}
}
輸出結(jié)果如下:
java.io.NotSerializableException: com.minghui.test.OtherClass
serialVersionUID
在上面的例子中拓萌,修改讀程序TestClass中的serialVersionUID:
public static final long serialVersionUID = 2L;
結(jié)果如下:
java.io.InvalidClassException: com.minghui.test.TestClass; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:621)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at com.minghui.test.SerilazationReadTest.main(SerilazationReadTest.java:15)
在反序列化的過程中,虛擬機會對比恢復字節(jié)流中的serialVersionUID和本地類中的serialVersionUID巡莹,僅當相同時才認為兩者是相同的類司志。
子類的序列化
如果一個類A繼承自類B,A實現(xiàn)了Serializable接口降宅,B未實現(xiàn)骂远。那么子類中的成員可以被序列化,父類中的成員無法被序列化腰根,反序列化后父類成員的初始值規(guī)則如下:
- 如果父類有默認構(gòu)造函數(shù)激才,則初始值為默認構(gòu)造函數(shù)中的賦值
- 如果父類沒有默認構(gòu)造函數(shù),則初始值為默認值
修改TestClass使其繼承與TestParent:
class TestClass extends TestParent implements Serializable {
public static final long serialVersionUID = 1L;
public static String sStaticField = "static field";
public String mName = "TestClass";
}
public class TestParent {
public String mParentField = "Uinitialized";
}
修改寫程序,增加為mParentField賦值的語句:
public class SerilizationWriteTest {
public static void main(String[] args) {
TestClass myTest = new TestClass();
myTest.mName = "New Name";
myTest.mParentField = "Runtime Changed";
TestClass.sStaticField = "New Static Value";
//... 同之前的例子
}
修改都程序的中的log打尤衬铡:
public class SerilazationReadTest {
public static void main(String[] args) {
ObjectInputStream input = null;
try {
input = new ObjectInputStream(new FileInputStream("D:/result"));
TestClass test = (TestClass) input.readObject();
System.out.println("SerilazationREAD name:" + test.mName);
System.out.println("SerilazationREAD parent field:" + test.mParentField);
System.out.println("SerilazationREAD sStaticField:" + TestClass.sStaticField);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
輸出結(jié)果如下:
SerilazationREAD name:New Name
SerilazationREAD parent field:Uinitialized
SerilazationREAD sStaticField:static field
為TestParent增加默認構(gòu)造函數(shù)劣挫,輸出如下:
SerilazationREAD name:New Name
SerilazationREAD parent field:Constructor Initialized
SerilazationREAD sStaticField:static field
序列化過程的控制
序列化允許將對象變成字節(jié)流傳輸,有時為了安全等方面的考慮东帅,并不需要對所有字段進行序列化压固,有時需要在序列化的過程中對部分字段做特殊處理(例如加密/解密),JDK提供了一系列方法用于控制序列化的過程靠闭。
transient關(guān)鍵字
當成員修飾符為為transient時帐我,JDK不會序列化該成員,TestClass中的mName增加transient修飾符
class TestClass implements Serializable {
public static final long serialVersionUID = 1L;
public static String sStaticField = "static field";
public transient String mName = "TestClass";
public TestClass() {
mName = "constructor init";
}
}
輸出如下:
SerilazationREAD name:null
SerilazationREAD sStaticField:static field
結(jié)合前面一節(jié)愧膀,可以總結(jié)出拦键,避免成員變量被序列化的方法有兩種:
- 將不希望被序列化的變量放到父類
- 為不希望被序列化的變量添加transient修飾符
WriteObject & ReadObject
實現(xiàn)了Serilaizable接口的類,可以通過添加WriteObject和ReadObject方法來控制序列化的過程檩淋,例子如下:
class TestClass implements Serializable {
public static final long serialVersionUID = 1L;
public static String sStaticField = "static field";
public String mName = "TestClass";
private void writeObject(ObjectOutputStream out) {
try {
PutField pt = out.putFields();
System.out.println("Before encypt name :" + mName);
String name = encyptString(mName);
System.out.println("after encypt name :" + name);
pt.put("Name", name);
out.writeFields();
} catch (IOException e) {
e.printStackTrace();
}
}
private void readObject(ObjectInputStream in) {
try {
GetField gt = in.readFields();
String name = (String) gt.get("mName", "");
System.out.println("Before decypt name :" + name);
mName = decyptString(name);
System.out.println("after decypt name :" + mName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static String encyptString(String original) {
StringBuilder sb = new StringBuilder();
int len = original.length();
for (int i = 0; i < len; i++) {
char c = (char) (original.charAt(i) + 1);
sb.append(c);
}
return sb.toString();
}
private static String decyptString(String original) {
StringBuilder sb = new StringBuilder();
int len = original.length();
for (int i = 0; i < len; i++) {
char c = (char) (original.charAt(i) - 1);
sb.append(c);
}
return sb.toString();
}
}
寫程序的輸出如下:
Before encypt name :New Name
After encypt name :Ofx!Obnf
讀程序的輸出如下:
Before decypt name :Ofx!Obnf
after decypt name :New Name
SerilazationREAD name:New Name
SerilazationREAD sStaticField:static field
需要注意的一點是,GetField的get方法以及PutField的put方法芬为,第一個參數(shù)都是類中成員變量的名稱。
如果將mName添加transient蟀悦,結(jié)果是什么媚朦?
Exception in thread "main" java.lang.IllegalArgumentException: no such field mName with type class java.lang.Object
總結(jié)
- 實現(xiàn)Serilzable的類可以被序列化/反序列化
- 實現(xiàn)Serilzable的類的成員必須都也實現(xiàn)了Serilzable接口
- 如果父類未實現(xiàn)Serilzable接口,子類實現(xiàn)了Serilzable接口熬芜,反序列化時莲镣,父類成員的值為默認構(gòu)造函數(shù)中的賦值,或默認值
- transient修飾的關(guān)鍵字不能被序列化
- 可以通過為類添加WriteObject & ReadObject控制序列化/反序列化過程
- 使用WriteObject & ReadObject序列化/反序列化時無法控制transient修飾的成員
- 使用WriteObject & ReadObject序列化/反序列化時涎拉,可以繞過條目2種的限制