什么是序列化
所謂的序列化,即把java對象以二進制形式保存到內(nèi)存、文件或者進行網(wǎng)絡傳輸。從二進制的形式恢復成為java對象,即反序列化。
通過序列化可以將對象持久化,或者從一個地方傳輸?shù)搅硪粋€地方。這方面的應用有RMI,遠程方法調(diào)用。
java中實現(xiàn)序列化有兩種方式,實現(xiàn)Serializable接口或者Externalizable接口。這篇總結(jié)只討論Serializable的情況。
public class SerializeTest implements Serializable{
private static final long serialVersionUID = 1L;
public String str;
public SerializeTest(String str) {
this.str = str;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
SerializeTest test = new SerializeTest("hello");
ByteArrayOutputStream oStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(oStream);
objectOutputStream.writeObject(test);//序列化
ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(oStream.toByteArray()));
SerializeTest obj = (SerializeTest) inputStream.readObject();//反序列化
System.out.println(obj.str);
}
}
上面的代碼演示了類SerializeTest實現(xiàn)序列化和反序列化的過程。
所有的序列化和反序列化過程都是java默認實現(xiàn)的,你只需要實現(xiàn)接口Serializable,就能得到一個實現(xiàn)了序列化的類。
通過ObjectOutputStream和ObjectInputStream分別將序列化對象輸出或者寫入到某個流當中。流的目的地可以是內(nèi)存字節(jié)數(shù)組(上例)、文件、或者網(wǎng)絡。
下面研究一下序列化過程中的幾個問題:
靜態(tài)變量如何序列化
public class SeriaUtil {
ByteArrayInputStream bInputStream;
ByteArrayOutputStream byOutputStream;
ObjectOutputStream outputStream ;
ObjectInputStream inputStream;
public void seria(Object test) throws IOException {
if (byOutputStream == null) {
byOutputStream = new ByteArrayOutputStream();
}
if (outputStream == null) {
outputStream = new ObjectOutputStream(byOutputStream);
}
outputStream.writeObject(test);
// System.out.println(byOutputStream.toByteArray().length);
}
public Object reSeria() throws IOException, ClassNotFoundException {
if (bInputStream == null) {
bInputStream = new ByteArrayInputStream(byOutputStream.toByteArray());
}
if (inputStream == null) {
inputStream = new ObjectInputStream(bInputStream);
}
Object obj = inputStream.readObject();
return obj;
}
}
public class StaticTest implements Serializable{
private static final long serialVersionUID = 1L;
public static int A = 0;
public static String B = "hello";
public static void main(String[] args) throws IOException, ClassNotFoundException {
//先序列化類,此時 A=0 B = hello
SeriaUtil seriaUtil = new SeriaUtil();
StaticTest test = new StaticTest();
seriaUtil.seria(test);
//修改 靜態(tài)變量的值
StaticTest.A = 1;
StaticTest.B = "world";
StaticTest obj = (StaticTest) seriaUtil.reSeria();
//輸出 A = 1 B = world
System.out.println(obj.A);
System.out.println(obj.B);
}
}
上面的代碼說明了,靜態(tài)變量不會被序列化。
序列化StaticTest實例test時,靜態(tài)變量 A=0 B="hello",序列化之后,修改StaticTest類的靜態(tài)變量值,A=1 B="world",
此時反序列化得到之前序列化的實例對象賦給obj,發(fā)現(xiàn)obj的靜態(tài)變量變?yōu)锳=1 B="world",說明靜態(tài)變量并未序列化成功。
事實上,在序列化對象時,會忽略對象中的靜態(tài)變量。很好理解,靜態(tài)變量是屬于類的,而不是某個對象的狀態(tài)。我們序列化面向的是對象,是想要將對象的狀態(tài)保存下來,所以
靜態(tài)變量不會被序列化。反序列化得到的對象中的靜態(tài)變量的值是當前jvm中靜態(tài)變量的值。靜態(tài)變量對于同一個jvm中同一個類加載器加載的類來說,是一樣的。對于同一個靜態(tài)變量,不會存在同一個類的不同實例擁有不同的值。
同一對象序列化兩次,反序列化后得到的兩個對象是否相等
這個問題提到的相等,是指是否為同一對象,即==關(guān)系
在某些情況下,確保這種關(guān)系是很重要的。比如王經(jīng)理和李經(jīng)理擁有同一個辦公室,即存在引用關(guān)系:
public class Manager{
Room room;
public Manager(Room r){
room = r;
}
}
public class Room{}
public class APP{
public void main(String args[]){
Room room = new Room();
Manager wang = new Manager(room);
Manager li = new Manager(room);
}
}
反序列化之后,wang,li,room的這種引用關(guān)系不應該發(fā)生變化。通過代碼驗證一下:
public class ReferenceTest implements Serializable{
public String a;
public ReferenceTest() {
a = "hah";
}
public static void main(String[] args) throws Exception {
System.out.println("構(gòu)造對象********************");
ReferenceTest test = new ReferenceTest();
System.out.println("序列化**********************");
SeriaUtil util = new SeriaUtil();
util.seria(test);
util.seria(test);//第二次序列化該對象
System.out.println("反序列化**********************");
ReferenceTest obj = (ReferenceTest) util.reSeria();
ReferenceTest obj1 = (ReferenceTest) util.reSeria();
System.out.println(obj == obj1);//true
System.out.println(obj == test);//false
}
}
上面的例子證明(System.out.println(obj == obj1);//true),同一對象序列化多次之后,反序列化得到的多個對象相等,即內(nèi)存地址一致。
使用同一個ObjectOutputStream對象序列化某個實例時,如果該實例還沒有被序列化過,則序列化,若之前已經(jīng)序列化過,則不再進行序列化,只是做一個標記而已。
所以在反序列化時,可以保持原有的引用關(guān)系。
System.out.println(obj == test);//false 也可以理解,反序列化之后重建了該對象,內(nèi)存地址必然是新分配的,故obj != test
父類沒有實現(xiàn)Serializable,父類中的變量如何序列化
public class SuperTest{
public String superB;
public SuperTest() {
superB = "hehe";
System.out.println("super 無參構(gòu)造函數(shù)");
}
public SuperTest(String b){
System.out.println("super 有參構(gòu)造函數(shù)");
superB = b;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("構(gòu)造對象*******************");
SonTest sonTest = new SonTest("son", "super");
System.out.println("序列化*********************");
SeriaUtil seriaUtil = new SeriaUtil();
seriaUtil.seria(sonTest);
System.out.println("反序列化******************");
SonTest obj = (SonTest) seriaUtil.reSeria();
System.out.println(obj.sonA);
System.out.println(obj.superB);
}
}
class SonTest extends SuperTest implements Serializable{
private static final long serialVersionUID = 1L;
public String sonA;
public SonTest() {
System.out.println("son 無參構(gòu)造函數(shù)");
}
public SonTest(String a, String b) {
super(b);
System.out.println("son 有參構(gòu)造函數(shù)");
sonA = a;
}
}
輸出:
構(gòu)造對象*******************
super 有參構(gòu)造函數(shù)
son 有參構(gòu)造函數(shù)
序列化*********************
反序列化******************
super 無參構(gòu)造函數(shù)
son
hehe
通過上面的代碼可以看出,父類如果沒有實現(xiàn)serializable,反序列化時會調(diào)用父類的無參構(gòu)造函數(shù)初始化父類當中的變量。
所以,我們可以通過顯示聲明父類的無參構(gòu)造函數(shù),并在其中初始化變量值來控制反序列化后父類變量的值。
transient使用
實現(xiàn)了serializable接口的類在序列化時默認會將所有的非靜態(tài)變量進行序列化。我們可以控制某些字段不被默認的序列化機制序列化。
比如,有些字段是跟當前系統(tǒng)環(huán)境相關(guān)的或者涉及到隱私的,需要保密的。這些字段是不可以被序列化到文件中或者通過網(wǎng)絡傳輸?shù)摹N覀兛梢酝ㄟ^為這些字段聲明
transient關(guān)鍵字,保證其不被序列化。
被關(guān)鍵字transient聲明的變量不會被序列化,反序列化時該變量會被自動填充為null(int 為0)。我們也可以為這些字段實現(xiàn)自己的序列化機制。
public class TransientTest implements Serializable{
private static final long serialVersionUID = 1L;
public transient String str;
public TransientTest() {
str = "hello";
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
String encryption = "key" + str;
out.writeObject(encryption);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
String encryption = (String) in.readObject();
str = encryption.substring("key".length(), encryption.length());
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
TransientTest test = new TransientTest();
SeriaUtil util = new SeriaUtil();
util.seria(test);
TransientTest reSeria = (TransientTest) util.reSeria();
System.out.println(reSeria.str);//hello
}
}
通過實現(xiàn)writeObject和readObject實現(xiàn)自己的序列化機制。上面的代碼模擬了一個加密的序列化過程。
成員變量沒有實現(xiàn)序列化
序列化某個實例時,如果這個實例含有對象類型的成員變量,那么同時會觸發(fā)該變量的序列化機制。這時就要求這個成員變量也實現(xiàn)Serializable接口,如果沒有實現(xiàn)該接口,拋出異常。
public class VariableTest implements Serializable{
Variable variable ;
public VariableTest() {
variable = new Variable();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("構(gòu)造對象*************");
VariableTest variableTest = new VariableTest();
System.out.println("序列化**************");
SeriaUtil util = new SeriaUtil();
//拋出異常:java.io.NotSerializableException
util.seria(variableTest);
System.out.println("反序列化****************");
VariableTest obj = (VariableTest) util.reSeria();
System.out.println(obj.variable.a);//拋出異常:Exception in thread "main" java.io.NotSerializableException: space.kyu.Variable
}
}
class Variable {
public String a;
public Variable(){
a = "hehe";
}
}
單例模式下的序列化
public class SingleTest implements Serializable{
public static SingleTest instance = new SingleTest();
private SingleTest(){}
public static void main(String[] args) throws IOException, ClassNotFoundException {
SingleTest test = SingleTest.instance;
SeriaUtil util = new SeriaUtil();
util.seria(test);
SingleTest reSeria = (SingleTest) util.reSeria();
System.out.println(reSeria == SingleTest.instance);//false
}
}
由上面的代碼可以看出,有兩個SingleTest實例同時存在,通過反序列化破壞了單例模式。反序列化時會開辟新的內(nèi)存空間重新實例化對象,所以單例模式被破壞。
為了解決這種問題,可以實現(xiàn)readResolve()方法。
public class SingleTest implements Serializable{
public static SingleTest instance = new SingleTest();
private SingleTest(){}
private Object readResolve() {
return SingleTest.instance;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
SingleTest test = SingleTest.instance;
SeriaUtil util = new SeriaUtil();
util.seria(test);
SingleTest reSeria = (SingleTest) util.reSeria();
System.out.println(reSeria == SingleTest.instance);//true
}
}
序列化版本
代碼是在不斷的演化的。1.1版本的類可以讀取1.0版本的序列化文件嗎?這就涉及到序列化的版本管理。
每個序列化版本都有其唯一的ID,他是數(shù)據(jù)域類型和方法簽名的指紋。當類的定義產(chǎn)生變化時,他的指紋也會跟著產(chǎn)生變化,對象流將拒絕讀入具有不同指紋的對象。
如果想保持早期版本的兼容,首先要獲取這個類早期版本的指紋。
我們可以使用 jdk自帶的工具 serialver 獲得這個指紋:serialver Test
staic final long serialVersionUID = -1423859403827594712L
然后將1.1版本中Test類的serialVersionUID常量定義為上面的值,即可序列化老版本的代碼。
如果一個類具有名為serialVersionUID的常量,那么java就不會再主動計算這個值,而是直接將其作為這個版本類的指紋。沒有特殊要求的話,一般都顯示的聲明serialVersionUID:private static final long serialVersionUID = 1L;來保證兼容性
如果對象流中的對象具有在當前版本中沒有的數(shù)據(jù)域,那么對象流會忽略這些數(shù)據(jù);如果當前版本具有對象流中所沒有的數(shù)據(jù)域,那么這些新加的域?qū)⒈辉O為默認值。
序列化與克隆
反序列化重新構(gòu)建對象的機制提供了一種克隆對象的簡便途徑,只要對應的類可序列化即可。
做法很簡單:直接將對象序列化到輸出流當中,然后將其讀回。這樣產(chǎn)生的對象是對現(xiàn)有對象的一個深拷貝。
public class CloneTest implements Serializable, Cloneable {
public String str;
public CloneTest(String str) {
this.str =str;
}
@Override
protected Object clone() throws CloneNotSupportedException {
SeriaUtil util = new SeriaUtil();
try {
util.seria(this);
CloneTest reSeria = (CloneTest) util.reSeria();
return reSeria;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) throws CloneNotSupportedException {
CloneTest test = new CloneTest("hi");
CloneTest clone = (CloneTest) test.clone();
System.out.println(clone.str);//hi
System.out.println(clone == test);//false
}
}
這樣克隆對象的優(yōu)點是簡單,缺點是比普通的克隆實現(xiàn)要慢的多。