【Java基礎】序列化與反序列化深入分析

leesf發表於2016-03-28

一、前言

  複習Java基礎知識點的序列化與反序列化過程,整理了如下學習筆記。

二、為什麼需要序列化與反序列化

  程式執行時,只要需要,物件可以一直存在,並且我們可以隨時訪問物件的一些狀態資訊,如果程式終止,那麼物件是肯定不會存在的,但是有時候,我們需要再程式終止時儲存物件的狀態資訊,之後程式再次執行時可以重新恢復到之前的狀態,如,玩家玩遊戲退出時,需要儲存玩家的狀態資訊(如等級、裝備等等),之後玩家再此登入時,必須要恢復這些狀態資訊。我們可以通過資料庫手段來達到這個儲存狀態的目的,在Java中,我們有更簡便的方法進行處理,那就是序列化與反序列化。序列化是一種物件持久化的手段,反序列化與序列化相反,其是通過序列化後的資訊重新組裝成物件。序列化與反序列化普遍應用在網路傳輸、RMI等場景中。

三、序列化概述

  3.1 序列化類結構圖

  下面展示了與序列化相關的類的結構圖

  說明:虛線框的表示介面型別,實線框表示具體的類。

  3.2 序列化關鍵字說明

  與序列化相關的關鍵字如下

  說明:

  1. 關鍵字transient,用來修飾欄位,表示此欄位在預設序列化過程中不會被處理,但是可以採用另外的手段進行處理。

  2. 關鍵字serialVersionUID,表示序列化版本號,當兩個類的序列化ID一致時允許反序列化,預設可以採用編譯器提供的值1L。

  3.3 序列化方法說明

  與序列化相關的方法如下

  說明:writeObject與readObject方法分別在ObjectOutput介面與ObjectInput介面中宣告,在ObjectOutputStream與ObjectInputStream中實現。

四、Serializable

  4.1 Serializable定義

  Serializable定義如下  

public interface Serializable {
}

  說明:Serializable為一個介面,並且沒有任何欄位和方法,僅僅作為一個標識。

  4.2 使用說明

  當序列化物件時,只需要將物件標記為可序列化,即實現介面Serializable即可。下面的Person類實現了Serializable介面。 

package com.hust.grid.leesf.serializable;

import java.io.Serializable;

public class Person implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String name;
    private String gender;
    private int age;
    private transient Person friend;


    public Person() {
        
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    public Person getFriend() {
        return friend;
    }
    
    public void setFriend(Person friend) {
        this.friend = friend;
    }

    @Override
    public String toString() {
        return "name = " + name + ", gender = " + gender + ", age = " + age
                + ", friend info is [" + friend + "]";
    }
}
View Code

  Person類的friend欄位設定為transient,表明不會被序列化,定義完Person類之後,我們即可以對Person類進行序列化與反序列化操作了,具體程式碼如下 

package com.hust.grid.leesf.serializable;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializableDemo {
    public static void main(String[] args) throws Exception {
        Person leesf = new Person();
        Person dyd = new Person();
        leesf.setAge(24);
        leesf.setGender("man");
        leesf.setName("leesf");
        
        dyd.setAge(24);
        dyd.setGender("woman");
        dyd.setName("dyd");    
        
        leesf.setFriend(dyd);
        dyd.setFriend(null);
        
        File file = new File("test");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        oos.writeObject(leesf);
        oos.flush();
        oos.close();
        System.out.println(leesf);
            
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        leesf = (Person) ois.readObject();
        ois.close();    
        System.out.println(leesf);    
    }
}
View Code

  執行結果如下  

name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]
name = leesf, gender = man, age = 24, friend info is [null]

  說明:由於friend欄位標記為transient,則預設序列化操作時不會進行序列化,反序列化後其值為null。

  4.3 問題說明

  1. Person類不實現Serializable介面

  若Person類不實現Serializable介面,進行序列化時,會發生什麼,會出現如下異常。 

Exception in thread "main" java.io.NotSerializableException:****

  表示Person沒有實現Serializable介面,具體原因如下

  在呼叫writeObject方法後,會經過一系列的呼叫,具體的呼叫棧如下

  說明:擷取了writeObject0函式中的一段程式碼,可以看到會檢查該物件是否是Serializable型別,不是,則會丟擲異常。

  2. 處理transient物件

  當欄位被transient修飾時,採用預設的序列化機制將不會對其進行處理,但是,如果要序列化transient欄位時,如何做呢,可以在要進行序列化的類中新增writeObject和readObject方法,其方法簽名如下 

private void writeObject(ObjectOutputStream stream) throws IOException
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException

  說明:注意,writeObject與readObject是採用private修飾符修飾的,說明,此方法只能在該類的其他方法中被呼叫,其他類中不能呼叫此方法,那麼當呼叫ObjectOutputStream的writeObject方法時,如何呼叫到此方法來執行使用者自定義處理邏輯的呢,答案是反射。利用反射可以在別的類中呼叫到此類中私有的方法,反射很強大。

  利用這個方法,我們修改Person類如下  

package com.hust.grid.leesf.serializable;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Person implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String name;
    private String gender;
    private int age;
    private transient Person friend;


    public Person() {
        
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    public Person getFriend() {
        return friend;
    }
    
    public void setFriend(Person friend) {
        this.friend = friend;
    }

    @Override
    public String toString() {
        return "name = " + name + ", gender = " + gender + ", age = " + age
                + ", friend info is [" + friend + "]";
    }

    private void writeObject(ObjectOutputStream stream) 
            throws IOException {
        stream.defaultWriteObject();
        stream.writeObject(friend);
    }
    
    private void readObject(ObjectInputStream stream) 
            throws IOException, ClassNotFoundException {
        stream.defaultReadObject();
        friend = (Person) stream.readObject();
    }
}
View Code

  測試類的程式碼不做修改,執行結果如下  

name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]
name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]

  說明:在實現自定義的邏輯時,在writeObject方法中可以呼叫defaultWriteObject()方法實現預設序列化(序列化非transient欄位),可以單獨處理transient關鍵字;在readObject方法中可以呼叫defaultReadObject()方法實現預設反序列化,可以單獨處理transient關鍵字(需要賦值)。值得注意的是,writeObject方法中defaultWriteObject和處理transient關鍵字的邏輯必須與readObject中defaultReadObject和處理transient關鍵字的邏輯順序一致,否則會丟擲異常。

  在呼叫writeObject方法後,會經過一系列的呼叫,具體的呼叫棧如下

  說明:經過反射,最終會呼叫到在Person類中定義的writeObject方法。readObject方法的呼叫可以以此類比,不再累贅。

五、Externalizable

  除了使用Serializable介面進行序列化以外,還可以使用Externalizable介面來進行序列化。

  5.1 Externalizable定義

  Externalizable的定義如下  

public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

  說明:Externalizable實現了Serializable介面,並且新增了兩個方法writeExternal與readExternal,需要序列化的類需要實現Externalizable介面,並且重寫介面中定義的兩個方法。

  5.2 使用說明

  首先將序列化的類實現Externalizable介面並且重寫writeExternal與readExternal方法,並在這兩個方法中實現處理邏輯。我們定義Person類如下

package com.hust.grid.leesf.serializable;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Person implements Externalizable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String name;
    private String gender;
    private int age;
    private transient Person friend;
    
    public Person() {
        
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    public Person getFriend() {
        return friend;
    }
    
    public void setFriend(Person friend) {
        this.friend = friend;
    }

    @Override
    public String toString() {
        return "name = " + name + ", gender = " + gender + ", age = " + age
                + ", friend info is [" + friend + "]";
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeUTF(gender);
        out.writeInt(age);
        out.writeObject(friend);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        name = in.readUTF();
        gender = in.readUTF();
        age = in.readInt();
        friend = (Person) in.readObject();        
    }    
}
View Code

  說明:Person類實現了Externalizable介面,重寫了writeExternal與readExternal方法,並且實現了使用者自定義序列化與反序列化邏輯。測試類程式碼不變,執行結果如下: 

name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]
name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]

  說明:從結果可知,成功進行了序列化與反序列化過程。值得注意的是,我們必須要給Person類提供一個無參構造器,才能正確完成序列化與反序列化過程。否則會丟擲如下異常

  修改Person類如下

package com.hust.grid.leesf.serializable;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Person implements Externalizable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String name;
    private String gender;
    private int age;
    private transient Person friend;
    
    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    public Person getFriend() {
        return friend;
    }
    
    public void setFriend(Person friend) {
        this.friend = friend;
    }

    @Override
    public String toString() {
        return "name = " + name + ", gender = " + gender + ", age = " + age
                + ", friend info is [" + friend + "]";
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeUTF(gender);
        out.writeInt(age);
        out.writeObject(friend);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        name = in.readUTF();
        gender = in.readUTF();
        age = in.readInt();
        friend = (Person) in.readObject();        
    }    
}
View Code

  說明:提供一個引數的建構函式,沒有無參建構函式,修改測試類程式碼如下  

package com.hust.grid.leesf.serializable;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializableDemo {
    public static void main(String[] args) throws Exception {
        Person leesf = new Person("leesf");
        Person dyd = new Person("dyd");
        leesf.setAge(24);
        leesf.setGender("man");
        leesf.setName("leesf");
        
        dyd.setAge(24);
        dyd.setGender("woman");
        dyd.setName("dyd");    
        
        leesf.setFriend(dyd);
        dyd.setFriend(null);
        
        File file = new File("test");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        oos.writeObject(leesf);
        oos.flush();
        oos.close();
        System.out.println(leesf);
            
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        leesf = (Person) ois.readObject();
        ois.close();    
        System.out.println(leesf);    
    }
}
View Code

  執行結果如下

name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]
Exception in thread "main" java.io.InvalidClassException: com.hust.grid.leesf.serializable.Person; no valid constructor
    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
    at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:768)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1775)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
    at com.hust.grid.leesf.serializable.SerializableDemo.main(SerializableDemo.java:32)

  說明:在反序列化的過程丟擲了異常,可以看出是Person類沒有合法的構造器,合法的構造器就是指無參構造器。當提供了無參構造器之後,就可以正確執行。

  5.3 問題說明

  1. Externalizable,writeObject與readObject方法

  如果Person類實現了Externalizable介面,並且在Person類中新增了writeObject與readObject方法,那麼在進行序列化與反序列化時,是以哪種方法為準呢,修改Person類如下

package com.hust.grid.leesf.serializable;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class Person implements Externalizable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String name;
    private String gender;
    private int age;
    private transient Person friend;
    
    public Person() {
        
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    public Person getFriend() {
        return friend;
    }
    
    public void setFriend(Person friend) {
        this.friend = friend;
    }

    @Override
    public String toString() {
        return "name = " + name + ", gender = " + gender + ", age = " + age
                + ", friend info is [" + friend + "]";
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("use writeExternal method");
        out.writeUTF(name);
        out.writeUTF(gender);
        out.writeInt(age);
        out.writeObject(friend);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        System.out.println("use readExternal method");
        name = in.readUTF();
        gender = in.readUTF();
        age = in.readInt();
        friend = (Person) in.readObject();        
    }    
    
    private void writeObject(ObjectOutputStream stream) 
            throws IOException {
        System.out.println("use writeObject method");
        stream.defaultWriteObject();
        stream.writeObject(friend);
    }
    
    private void readObject(ObjectInputStream stream) 
            throws IOException, ClassNotFoundException {
        System.out.println("use readObject method");
        stream.defaultReadObject();
        friend = (Person) stream.readObject();
    }
}
View Code

  說明:在方法中新增了列印語句,這樣就可以輕易判別採用的何種方式。測試類程式碼如下  

package com.hust.grid.leesf.serializable;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializableDemo {
    public static void main(String[] args) throws Exception {
        Person leesf = new Person();
        Person dyd = new Person();
        leesf.setAge(24);
        leesf.setGender("man");
        leesf.setName("leesf");
        
        dyd.setAge(24);
        dyd.setGender("woman");
        dyd.setName("dyd");    
        
        leesf.setFriend(dyd);
        dyd.setFriend(null);
        
        File file = new File("test");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        oos.writeObject(leesf);
        oos.flush();
        oos.close();
        System.out.println(leesf);
            
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        leesf = (Person) ois.readObject();
        ois.close();    
        System.out.println(leesf);    
    }
}
View Code

  執行結果

use writeExternal method
use writeExternal method
name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]
use readExternal method
use readExternal method
name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]

  說明:從結果可以看出,是以Externalizable介面中定義的兩個方法進行序列化與反序列化的,這時,讀者可能又會有另外一個疑問,那就是為什麼會列印兩次呢?答案是因為該方法被呼叫了兩次,因為Person類有一個Person域,會導致呼叫兩次。

  2. 處理transient欄位

  可以在writeExternal與readExternal方法中實現自定義邏輯,對transient欄位進行序列化與反序列化。

六、序列化問題

  6.1 採用預設序列化機制,類的靜態欄位會被序列化嗎?

  採用預設序列化機制進行序列化時,類的靜態欄位會被序列化嗎,此時類的靜態欄位不會被序列化,當然,我們可以採用自定義序列化邏輯對靜態變數進行序列化。

  6.2 父類序列化問題

  採用預設序列化機制序列化子類時,其父類的欄位會被序列化嗎?可以分為如下情形

  1. 父類沒有實現Serializable介面,沒有提供預設建構函式

  這時,反序列化會出錯,提示沒有提供正確的建構函式。修改Person類,程式碼如下

package com.hust.grid.leesf.serializable;

import java.io.Serializable;

class Human {
    private int number;
    
    public Human(int number) {
        this.number = number;
    }
    
    public int getNumber() {
        return number;
    }
    
    public void setNumber(int number) {
        this.number = number;
    }
    
    public String toString() {
        return "number = " + number;
    }
}

public class Person extends Human implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String name;
    private String gender;
    private int age;
    private transient Person friend;
    
    
    
    
    public Person(int number, String name) {
        super(number);
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    public Person getFriend() {
        return friend;
    }
    
    public void setFriend(Person friend) {
        this.friend = friend;
    }

    @Override
    public String toString() {
        return super.toString() + ", name = " + name + ", gender = " + gender + ", age = " + age
                + ", friend info is [" + friend + "]";
    }    
}
View Code

  測試類的的程式碼如下 

package com.hust.grid.leesf.serializable;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializableDemo {
    public static void main(String[] args) throws Exception {
        Person leesf = new Person(1, "leesf");
        Person dyd = new Person(2, "dyd");
        leesf.setAge(24);
        leesf.setGender("man");
        leesf.setName("leesf");
        
        dyd.setAge(24);
        dyd.setGender("woman");
        dyd.setName("dyd");    
        
        leesf.setFriend(dyd);
        dyd.setFriend(null);
        
        File file = new File("test");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        oos.writeObject(leesf);
        oos.flush();
        oos.close();
        System.out.println(leesf);
                    
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        leesf = (Person) ois.readObject();
        ois.close();    
        System.out.println(leesf);    
    }
}
View Code

  執行結果 

number = 1, name = leesf, gender = man, age = 24, friend info is [number = 2, name = dyd, gender = woman, age = 24, friend info is [null]]
Exception in thread "main" java.io.InvalidClassException: com.hust.grid.leesf.serializable.Person; no valid constructor
    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
    at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:768)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1775)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
    at com.hust.grid.leesf.serializable.SerializableDemo.main(SerializableDemo.java:32)

  說明:可以看出是沒有提供合法的建構函式。

  2. 父類沒有實現Serializable介面,提供預設建構函式

  第一步中出現了錯誤,此時,我們修改Person類,程式碼如下  

package com.hust.grid.leesf.serializable;

import java.io.Serializable;

class Human {
    private int number;
    public Human() {
        
    }
    public Human(int number) {
        this.number = number;
    }
    
    public int getNumber() {
        return number;
    }
    
    public void setNumber(int number) {
        this.number = number;
    }
    
    public String toString() {
        return "number = " + number;
    }
}
public class Person extends Human implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String name;
    private String gender;
    private int age;
    private transient Person friend;
    
    public Person() {
        super();
    }
    
    public Person(int number, String name) {
        super(number);
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    public Person getFriend() {
        return friend;
    }
    
    public void setFriend(Person friend) {
        this.friend = friend;
    }

    @Override
    public String toString() {
        return super.toString() + ", name = " + name + ", gender = " + gender + ", age = " + age
                + ", friend info is [" + friend + "]";
    }    
}
View Code

  說明:給Human類提供了無參建構函式。測試類程式碼不變,執行結果如下

number = 1, name = leesf, gender = man, age = 24, friend info is [number = 2, name = dyd, gender = woman, age = 24, friend info is [null]]
number = 0, name = leesf, gender = man, age = 24, friend info is [null]

  說明:此時,我們可以看到,可以進行反序列化了,但是父類的number欄位被賦值為int的預設值0,Person類的transient欄位沒有被序列化。

  3. 父類實現Serializable介面

  當父類實現Serializable介面時,修改Person類程式碼如下 

package com.hust.grid.leesf.serializable;

import java.io.Serializable;

class Human implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private int number;
    public Human() {
        
    }
    public Human(int number) {
        this.number = number;
    }
    
    public int getNumber() {
        return number;
    }
    
    public void setNumber(int number) {
        this.number = number;
    }
    
    public String toString() {
        return "number = " + number;
    }
}
public class Person extends Human implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String name;
    private String gender;
    private int age;
    private transient Person friend;
    
    public Person() {
        super();
    }
    
    public Person(int number, String name) {
        super(number);
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    public Person getFriend() {
        return friend;
    }
    
    public void setFriend(Person friend) {
        this.friend = friend;
    }

    @Override
    public String toString() {
        return super.toString() + ", name = " + name + ", gender = " + gender + ", age = " + age
                + ", friend info is [" + friend + "]";
    }    
}
View Code

  測試類的程式碼不變,執行結果如下 

number = 1, name = leesf, gender = man, age = 24, friend info is [number = 2, name = dyd, gender = woman, age = 24, friend info is [null]]
number = 1, name = leesf, gender = man, age = 24, friend info is [null]

  說明:從結果可知,已經可以進行正確的序列化與反序列化了,子類的transient欄位沒有被序列化。

  6.3 共享物件序列化問題  

  當序列化的兩個物件都包含另外一個物件的引用時,在反序列化時,另外一個物件只會出現一次嗎?修改Person類程式碼如下  

package com.hust.grid.leesf.serializable;

import java.io.Serializable;

public class Person implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String name;
    private String gender;
    private int age;
    private transient Person friend;
    
    public Person() {
        
    }
    
    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    public Person getFriend() {
        return friend;
    }
    
    public void setFriend(Person friend) {
        this.friend = friend;
    }
}
View Code

  測試類程式碼如下 

package com.hust.grid.leesf.serializable;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

public class SerializableDemo {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws Exception {
        Person leesf = new Person("leesf");    
        Person dyd = new Person("dyd");
        Person lr = new Person("lr");
        leesf.setAge(24);
        leesf.setGender("man");
        
        dyd.setAge(24);
        dyd.setGender("woman");
        
        lr.setAge(25);
        lr.setGender("man");
        
        leesf.setFriend(dyd);
        lr.setFriend(dyd);
        dyd.setFriend(null);
        
        List<Person> persons = new ArrayList<Person>();
        persons.add(leesf);
        persons.add(dyd);
        persons.add(lr);
        
        ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
        ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
        
        ObjectOutputStream oos1 = new ObjectOutputStream(bos1);
        oos1.writeObject(persons);
        oos1.writeObject(persons);
        
        ObjectOutputStream oos2 = new ObjectOutputStream(bos2);
        oos2.writeObject(persons);
        
        ByteArrayInputStream bis1 = new ByteArrayInputStream(bos1.toByteArray());
        ByteArrayInputStream bis2 = new ByteArrayInputStream(bos2.toByteArray());
        
        ObjectInputStream ois1 = new ObjectInputStream(bis1);
        ObjectInputStream ois2 = new ObjectInputStream(bis2);
        
        List<Person> persons1 = (List<Person>) ois1.readObject();
        List<Person> persons2 = (List<Person>) ois1.readObject();
        List<Person> persons3 = (List<Person>) ois2.readObject();
        
        System.out.println(persons1);
        System.out.println(persons2);
        System.out.println(persons3);    
    }
}
View Code

  執行結果如下  

[com.hust.grid.leesf.serializable.Person@7f31245a, com.hust.grid.leesf.serializable.Person@6d6f6e28, com.hust.grid.leesf.serializable.Person@135fbaa4]
[com.hust.grid.leesf.serializable.Person@7f31245a, com.hust.grid.leesf.serializable.Person@6d6f6e28, com.hust.grid.leesf.serializable.Person@135fbaa4]
[com.hust.grid.leesf.serializable.Person@45ee12a7, com.hust.grid.leesf.serializable.Person@330bedb4, com.hust.grid.leesf.serializable.Person@2503dbd3]

  說明:從結果可知,oos1執行的writeObject是向同一個記憶體空間寫了兩次,從結果可看出,兩次寫入的物件的地址空間都是一樣的,即進行了淺拷貝。而oos2執行的writeObject是向另外一個記憶體空間寫了一次,從結果可看出,因為物件的地址不同於之前的物件地址,即採用了深拷貝。

七、總結

  寫到這裡,關於Java中的序列化與反序列化機制就已經分析完了,經過此次分析,對序列化機制的認識更加深刻。學習一個知識點,就要認認真真,踏踏實實的弄懂一個知識點,寫部落格就是一個特別好的方式。謝謝各位園友的觀看~

 

參考連結

http://blog.csdn.net/jiangwei0910410003/article/details/18989711/

http://www.hollischuang.com/archives/1140

  

  

 

相關文章