transient 介紹
某天無意間看到了 ArrayList 的 source code,發現了一個沒看過的關鍵字:transient。寫 Android 也有幾年了,卻對 transient 完全沒概念。 Google 了一下,才知道這個關鍵字通常都是搭配 Serializable 來使用的。
Interface 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、nativeInterface Serializable
留言
張貼留言