transient 介紹

        某天無意間看到了 ArrayList 的 source code,發現了一個沒看過的關鍵字:transient。寫 Android 也有幾年了,卻對 transient 完全沒概念。 Google 了一下,才知道這個關鍵字通常都是搭配 Serializable 來使用的。

 什麼是 Serializable 

        Serializable 是為了要讓物件能序列化而必需要實作的 marker interface。而序列化簡單說就是能將記憶體(Memory)中的實體物件(Object Instance)以位元流(byte stream)方式儲存於永久媒體如硬碟。之後也能從永久媒體讀取物件到記憶體,並回復到先前狀態。除了存在永久媒體之外,也可以用在網路溝通,如 socket 或 RMI。

 什麼是 transient 

        transient 為 Java 內部的一個關鍵字,其目的是告訴 JVM 有加 transient 的 member variable 不要被序列化。因為有時候我們會希望物件能序列化,但內部有些資料卻不要記錄,像是個人資料如密碼等。如以下的程式碼:
public class Foo implements Serializable
{
    private String saveMe;
    private transient SomeObject dontSaveMe;
    private transient String password;
    private transient int dontSaveInt;
    //...
}
        JVM 就不會把 class dontSaveMe,String password,int dontSaveInt 序列化。

 Java Serializable 序列化規則 

  • Class 中所有 primitive type 預設都是可以序列化。
  • 成員變數 (primitive type 或是其他 class) 只要宣告 transient,就不會被序列化。
  • 當一個 Class 可以被序列化 (有實作 Serializable)時,當成員變數有其他 class,假如沒宣告為 transient,也必需要實作 Serializable。
  • 宣告 static 的變數,不管有沒有加上 transient,都不能被序列化。

 實際例子:ArrayList 

        從 ArrayList 的 source code 可以看到,這個 class 有實作 Serializable,所以可以被序列化。但內部有資料不想被序列化,如以下:
/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer.
 */
 private transient Object[] elementData;
        有稍微挖一下 ArrayList source code 的人可能會覺得奇怪,elementData 是實際存放 list 資料的 array,他宣告了 transient,就表示他不會被序列化。整個 class 中最重要的資料不序列化,那 ArrayList 實作 Serializable 不就沒意義了?其實 ArrayList 是有自己處理物件序列化的行為。Serializable 的 Java document 中有提到,當你想自己處理如何序列化時,需要加上兩個特殊的 method (要一模一樣,signature 也不能改):
private void writeObject(java.io.ObjectOutputStream out)
     throws IOException
private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException;
因此我們可以回頭看看 ArrayList 的 source code,確實有加這兩個method:
/**
 * Save the state of the ArrayList instance to a stream (that
 * is, serialize it).
 *
 * @serialData The length of the array backing the ArrayList
 *             instance is emitted (int), followed by all of its elements
 *             (each an Object) in the proper order.
 */
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out array length
    s.writeInt(elementData.length);

    // Write out all elements in the proper order.
    for (int i = 0; i < size; i++)
        s.writeObject(elementData[i]);

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}
/**
 * Reconstitute the ArrayList instance from a stream (that is,
 * deserialize it).
 */
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in array length and allocate array
    int arrayLength = s.readInt();
    Object[] a = elementData = new Object[arrayLength];

    // Read in all elements in the proper order.
    for (int i = 0; i < size; i++)
        a[i] = s.readObject();
}
        那為什麼 ArrayList 要自己處理序列化呢?這 Google 一下就能找到解釋。主要的理由是為了 performance。我們知道 ArrayList 的長度是會因資料量而增加的,從 srouce code 可以知道,elementData 長度可能會大於實際有存資料的長度,要是直接用預設的序列化方式的話,elementData 就會原封不動的保留下來,這樣就造成空間的浪費了,所以我們可以從 writeObject 跟 readObject 得知,要存的時候只存實際的資料長度,回復時也只回復實際長度。既然最重要的 elementData 有其他考量要自行處理,那當然就可以讓他不用預設序列化,因此要加上 transient。

 參考資料 

關於修飾詞, ex..strictfp、transient、volatile、native
Interface Serializable

留言

這個網誌中的熱門文章

Java 泛型 (Generic) 的 Covariance, Contravariance, 以及 Invariance

為什麼 GUI framework 大多都是 single-threaded (not thread safe)