讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議12:避免用序列化類在構造函數中為不變量賦值 >

建議12:避免用序列化類在構造函數中為不變量賦值

我們知道帶有final標識的屬性是不變量,也就是說只能賦值一次,不能重複賦值,但是在序列化類中就有點複雜了,比如有這樣一個類:


public class Person implements Serializable{

private static final long serialVersionUID=71282334L;

//不變量

public fnal String name="混世魔王";

}


這個Person類(此時V1.0版本)被序列化,然後存儲在磁盤上,在反序列化時name屬性會重新計算其值(這與static變量不同,static變量壓根就沒有保存到數據流中),比如name屬性修改成了「德天使」(版本升級為V2.0),那麼反序列化對象的name值就是「德天使」。保持新舊對象的final變量相同,有利於代碼業務邏輯統一,這是序列化的基本規則之一,也就是說,如果final屬性是一個直接量,在反序列化時就會重新計算。對這基本規則不多說,我們要說的是final變量另外一種賦值方式:通過構造函數賦值。代碼如下:


public class Person implements Serializable{

private static final long serialVersionUID=91282334L;

//不變量初始不賦值

public final String name;

//構造函數為不變量賦值

public Person(){

name="混世魔王";

}

}


這也是我們常用的一種賦值方式,可以把這個Person類定義為版本V1.0,然後進行序列化,看看有什麼問題沒有,序列化的代碼如下所示:


public class Serialize{

public static void main(Stringargs){

//序列化以持久保存

SerializationUtils.writeObject(new Person());

}

}


Person的實例對像保存到了磁盤上,它是一個貧血對像(承載業務屬性定義,但不包含其行為定義),我們做一個簡單的模擬,修改一下name值代表變更,要注意的是serialVersionUID保持不變,修改後的代碼如下:


public class Person implements Serializable{

private static final long serialVersionUID=91282334L;

//不變量初始不賦值

public final String name;

//構造函數為不變量賦值

public Person(){

name="德天使";

}

}


此時Person類的版本是V2.0,但serialVersionUID沒有改變,仍然可以反序列化,其代碼如下:


public class Deserialize{

public static void main(Stringargs){

//反序列化

Person p=(Person)SerializationUtils.readObject();

System.out.println(p.name);

}

}


現在問題來了:打印的結果是什麼?是混世魔王還是德天使?

答案即將揭曉,答案是:混世魔王。

final類型的變量不是會重新計算嗎?答案應該是「德天使」才對啊,為什麼會是「混世魔王」?這是因為這裡觸及了反序列化的另一個規則:反序列化時構造函數不會執行。

反序列化的執行過程是這樣的:JVM從數據流中獲取一個Object對象,然後根據數據流中的類文件描述信息(在序列化時,保存到磁盤的對象文件中包含了類描述信息,注意是類描述信息,不是類)查看,發現是final變量,需要重新計算,於是引用Person類中的name值,而此時JVM又發現name竟然沒有賦值,不能引用,於是它很「聰明」地不再初始化,保持原值狀態,所以結果就是「混世魔王」了。

讀者不要以為這樣的情況很少發生,如果使用Java開發過桌面應用,特別是參與過對性能要求較高的項目(比如交易類項目),那麼很容易遇到這樣的問題。比如一個C/S結構的在線外匯交易系統,要求提供24小時的聯機服務,如果在升級的類中有一個final變量是構造函數賦值的,而且新舊版本還發生了變化,則在應用請求熱切的過程中(非常短暫,可能只有30秒),很可能就會出現反序列化生成的final變量值與新產生的實例值不相同的情況,於是業務異常就產生了,情況嚴重的話甚至會影響交易數據,那可是天大的事故了。

注意 在序列化類中,不使用構造函數為final變量賦值。