建議13:避免為final變量復雜賦值
為final變量賦值可以通過方法賦值,即直接在聲明時通過方法返回值賦值。
public class Person implements Serializable {
private static final long serialVerisionUID = 91282334L;
//通過方法返回值為final變量賦值
public final String name = initName();
//初始化方法名
public String initName(){
return "混世魔王";
// return "德天使";
}
}
先序列化上面的代碼,然后把initName的返回值改為注釋的代碼。然后在反序列化,name值是什么?
是“混世魔王”,雖然上一條建議說final變量會被重新賦值,其中的“值”指的是簡單對象,簡單對象包括:8個基本數據類型,以及數組,字符串(字符串情況很復雜,不通過new關鍵字生成String對象的情況下,final變量的賦值與基本類型相同),但是不能通過方法賦值。
其中的原理是這樣的,保存到磁盤上(或網絡傳輸)的對象文件包括兩部分:
(1)類描述信息
包括包路徑、繼承關系、訪問權限、變量描述、變量訪問權限、方法簽名、返回值,以及變量的關聯類信息。要注意的一點是,它并不是class文件的翻版,它不記錄方法、構造函數、static變量等的具體實現。之所以類描述會被保存,很簡單,是因為能去也能回嘛,這保證反序列化的健壯運行。
(2)非瞬態(transient關鍵字)和非靜態(static關鍵字)的實例變量值
注意,這里的值如果是一個基本類型,就是一個簡單值保存下來;如果是復雜對象,連該對象和關聯類信息一起保存,并且持續遞歸下去(關聯類也必須實現Serializable接口,否則會出現序列化異常),也就是說遞歸到最后,其實還是基本數據類型的保存。
正是因為這兩點原因,一個持久化后的對象文件會比一個class類文件大很多。
總結一下,反序列化時final變量在以下情況下不會被重新賦值:
- 通過構造函數為final變量賦值。
- 通過方法返回值為final變量賦值。
- final修飾的屬性不是基本類型。