讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議13:避免為final變量複雜賦值 >

建議13:避免為final變量複雜賦值

為final變量賦值還有一種方式:通過方法賦值,即直接在聲明時通過方法返回值賦值。還是以Person類為例來說明,代碼如下:


public class Person implements Serializable{

private static final long serialVersionUID=91282334L;

//通過方法返回值為final變量賦值

public fnal String name=initName();

//初始化方法名

public String initName(){

return"混世魔王";

}

}


name屬性是通過initName方法的返回值賦值的,這在複雜類中經常用到,這比使用構造函數賦值更簡潔、易修改,那麼如此用法在序列化時會不會有問題呢?我們一起來看看。Person類寫好了(定義為V1.0版本),先把它序列化,存儲到本地文件,其代碼與上一建議的Serialize類相同,不再贅述。

現在,Person類的代碼需要修改,initName的返回值也改變了,代碼如下:


public class Person implements Serializable{

private static final long serialVersionUID=91282334L;

//通過方法返回值為final變量賦值

public final String name=initName();

//初始化方法名

public String initName(){

return"德天使";

}

}


上段代碼僅僅修改了initName的返回值(Person類為V2.0版本),也就是說通過new生成的Person對象的final變量值都是「德天使」。那麼我們把之前存儲在磁盤上的實例加載上來,name值會是什麼呢?

結果是:混世魔王。很詫異,上一建議說過final變量會被重新賦值,但是這個例子又沒有重新賦值,為什麼?

上個建議所說final會被重新賦值,其中的「值」指的是簡單對象。簡單對像包括:8個基本類型,以及數組、字符串(字符串情況很複雜,不通過new關鍵字生成String對象的情況下,final變量的賦值與基本類型相同),但是不能方法賦值。

其中的原理是這樣的,保存到磁盤上(或網絡傳輸)的對象文件包括兩部分:

(1)類描述信息

包括包路徑、繼承關係、訪問權限、變量描述、變量訪問權限、方法簽名、返回值,以及變量的關聯類信息。要注意的一點是,它並不是class文件的翻版,它不記錄方法、構造函數、static變量等的具體實現。之所以類描述會被保存,很簡單,是因為能去也能回嘛,這保證反序列化的健壯運行。

(2)非瞬態(transient關鍵字)和非靜態(static關鍵字)的實例變量值

注意,這裡的值如果是一個基本類型,好說,就是一個簡單值保存下來;如果是複雜對象,也簡單,連該對像和關聯類信息一起保存,並且持續遞歸下去(關聯類也必須實現Serializable接口,否則會出現序列化異常),也就是說遞歸到最後,其實還是基本數據類型的保存。

正是因為這兩點原因,一個持久化後的對象文件會比一個class類文件大很多,有興趣的讀者可以自己寫個Hello word程序檢驗一下,其體積確實膨脹了不少。

總結一下,反序列化時final變量在以下情況下不會被重新賦值:

通過構造函數為final變量賦值。

通過方法返回值為final變量賦值。

final修飾的屬性不是基本類型。