Java——transient and 序列化

gary-liu發表於2017-03-14

序列化

序列化 (Serialization)是將物件的狀態資訊轉換為可以儲存或傳輸的形式的過程。
在網路傳輸過程中,可以是位元組或是XML,json等格式。而位元組的,XML,json編碼格式可以還原完全相等的物件。這個相反的過程又稱為反序列化。

transient

java 的transient關鍵字為我們提供了便利,你只需要實現Serilizable介面,將不需要序列化的屬性前新增關鍵字transient,序列化物件的時候,這個屬性就不會序列化到指定的目的地中。

序列化兩種方式

1.物件實現了序列化介面Serializable

2.第二種方式為實現介面Externalizable

其實這個介面也是繼承了Serializable介面,該介面中有兩個方法 void writeExternal(ObjectOutput out)void readExternal(ObjectInput in)
,所以使用這種方式,要實現這兩個介面。在 writeExternal() 方法裡定義了哪些屬性可以序列化,例如 out.writeObject(userName),哪些不可以序列化,物件在經過這裡就把規定能被序列化的序列化儲存檔案,不能序列化的不處理,然後在反序列的時候自動呼叫 readExternal() 方法,如userName=(String) in.readObject(),根據序列順序挨個讀取進行反序列,並自動封裝成物件返回。

可以看出來Externalizable形式的序列化會更靈活一些,可以自己定義哪些欄位需要序列化,和transient的功能有些類似。

程式碼示例

程式碼中有兩種實現方式,第一種的測試程式碼被註釋了。

public class SerializationPractice {

    public void serializableTest() {
        // Worker worker = new Worker();

        Worker1 worker = new Worker1();
        worker.setAge(30);
        worker.setName("gary");
        worker.setHeight(170);

        System.out.println(worker);

        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(
                new FileOutputStream("/Users/gary/Documents/serializeTest.txt"))) {
            objectOutputStream.writeObject(worker);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void deserializeTest() {
        try (ObjectInputStream objectInputStream = new ObjectInputStream(
                new FileInputStream("/Users/gary/Documents/serializeTest.txt"))) {
            // Worker worker = (Worker) objectInputStream.readObject();
            Worker1 worker = (Worker1) objectInputStream.readObject();
            System.out.println(worker);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        SerializationPractice practice = new SerializationPractice();
        //序列化
         practice.serializableTest();
        //反序列化
        //practice.deserializeTest();
    }

}

class Worker implements Serializable {

    private static final long serialVersionUID = 2L;

    private String name;
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {

        return "name=" + name + " ,age=" + age;
    }
}

class Worker1 implements Externalizable {

    private static final long serialVersionUID = 1L;

    private String name;
    private int age;
    private int height;

    public Worker1() {
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    @Override
    public String toString() {
        return "name=" + name + ",age=" + age + ",height=" + height;
    }

    @Override
    public void writeExternal(ObjectOutput objectOutput) {

        try {
            objectOutput.writeObject(name);
            objectOutput.writeInt(age);
            objectOutput.writeInt(height);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void readExternal(ObjectInput objectInput) {
        try {
            name = (String) objectInput.readObject();
            age = objectInput.readInt();
            height = objectInput.readInt();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

實現介面Externalizable的序列化時要注意一下情況:

  1. writeExternal方法中沒序列化的屬性,反序列化後得到的是屬性型別的預設值,和 transient 作用類似。

  2. 實現介面Externalizable的類要提供無引數的建構函式,否則會報下面的錯誤

java.io.InvalidClassException: javabase.serialization.Worker1; no valid constructor

在使用Externalizable進行序列化的時候,在讀取物件時,會呼叫被序列化類的無參構造器去建立一個新的物件,然後再將被儲存物件的欄位的值分別填充到新物件中。所以,實現Externalizable介面的類必須要提供一個public的無參的構造器。

序列化ID

虛擬機器是否允許反序列化,不僅取決於類路徑和功能程式碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID)。

序列化時 serialVersionUID=1L,然後反序列化是修改Worker類的 serialVersionUID=2L,就會報下面的錯誤。

java.io.InvalidClassException: javabase.serialization.Worker; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

總結

  1. 如果一個類想被序列化,需要實現Serializable介面或Externalizable介面。否則將丟擲NotSerializableException異常,這是因為,在序列化操作過程中會對型別進行檢查,要求被序列化的類必須屬於Enum、Array和Serializable型別其中的任何一種。
  2. 在變數宣告前加上該關鍵字,可以阻止該變數被序列化到檔案中。
  3. 在類中增加writeObject 和 readObject 方法可以實現自定義序列化策略
  4. 序列化並不儲存靜態變數。

參考資料

Java物件的序列化與反序列化

相關文章