網頁

2019/1/25

Java 物件序列化(Serialize),反序列化(Deserialize)

本篇使用Java 將物件序列化並輸出至檔案,然後將檔案反序列化為物件。

Java的序列化是指將一個物件(Object)序列化為位元組串流(Byte Streams),其包含了物件的型態和儲存值。目的為物件的持久化,也就是說"物件"可以被輸出為檔案,或儲存於資料庫。

由於序列化後的物件資訊是獨立於JVM外的,所以序列化物件可在兩個不同的平台間傳遞。


下面範例使用ObjectOutputStream.writeObject()將物件序列化輸出為檔案;使用ObjectInputStream.readObject()將檔案反序列化回物件。

要被序列化及反序列化的物件必須實作Serializable介面,否則在過程中會出現NotSerializableException例外錯誤。

繼承了Serializable會要求給定一個serialVersionUID,用來代表示序列化物件的版本。

package com.abc.demo;

import java.io.*;

public class Main {

    public static void main(String[] args) {

        // 物件序列化
        try (
                FileOutputStream fos = new FileOutputStream("D:/MyClass");
                ObjectOutputStream oos = new ObjectOutputStream(fos)
        ) {
            MyClass obj1 = new MyClass("hello world", 1000);
            System.out.println("obj1:" + obj1);
            oos.writeObject(obj1);
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 物件反序列化
        try (
                FileInputStream fis = new FileInputStream("D:/MyClass");
                ObjectInputStream ois = new ObjectInputStream(fis)
        ) {
            MyClass obj2;
            obj2 = (MyClass) ois.readObject();
            System.out.println("obj2 " + obj2);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

}

class MyClass implements Serializable {
    private static final long serialVersionUID = 1L;

    private String s;
    private int i;

    public MyClass(String s, int i) {
        this.s = s;
        this.i = i;
    }

    @Override
    public String toString() {
        return "s:" + s + ", i:" + i;
    }
}

執行結果如下

obj1:s:hello world, i:1000
obj2 s:hello world, i:1000

若不讓MyClass實作Serializable介面,則執行時發生錯誤如下。

obj1:s:hello world, i:1000
java.io.NotSerializableException: com.abc.demo.MyClass
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at com.abc.demo.Main.main(Main.java:16)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.abc.demo.MyClass
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1577)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
    at com.abc.demo.Main.main(Main.java:28)
Caused by: java.io.NotSerializableException: com.abc.demo.MyClass
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at com.abc.demo.Main.main(Main.java:16)

因為當ObjectOutputStream.writeObject()將物件寫出時會檢查此物件是否為Serializable的實例,若否則會丟出NotSerializableException。原始碼節錄如下。

...
            if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum<?>) obj, desc, unshared);
            } else if (obj instanceof Serializable) { // <---- 判斷是否為Serializable的實例
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName()); // <---- 丟出錯誤
                }
            }
...

若在序列化類別的成員變數前加上transient關鍵字,則該成員變數不會被序列化,例如在MyClass.i前面加上transient

class MyClass implements Serializable{
    private static final long serialVersionUID = 1L;
    private String s;
    transient private int i; // <---加上transient關鍵字

    public MyClass(String s, int i) {
        this.s = s;
        this.i = i;
    }

    @Override
    public String toString() {
        return "s:" + s + ", i:" + i;
    }
}

執行結果如下,可以看到因為i的值沒有被序列化,因此從檔案反序列化回物件後,i的值為初始值0。

obj1:s:hello world, i:1000
obj2 s:hello world, i:0


沒有留言:

張貼留言