1. 程式人生 > >序列化的一些注意事項及建議

序列化的一些注意事項及建議

本文來自《改善java的151個建議》

建議11:養成良好習慣,顯示宣告UID

我們先寫一個序列化與反序列化的工具類SerilizationUtils

public class SerializationUtils {

	private static String FILE_NAME="E:/serializable.txt";
	
	public static void writeObject(Serializable s){
		try{
			
			
			ObjectOutputStream oob = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
			oob.writeObject(s);
			oob.close();
		}catch(Exception e){};
	}
	
	public static Object readObject(){
		Object obj=null;
		try{
			
			ObjectInputStream oob = new ObjectInputStream(new FileInputStream(FILE_NAME));
			obj = oob.readObject();
			
			oob.close();
		}catch(Exception e){};
		
		return obj;
	}
}

Person類

public class Person implements Serializable  {

	/**
	 * 這裡先不顯示的宣告UID
	 */
	//private static final long serialVersionUID = 1L;

	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	//
	/*private String sex;

	public String getSex() {
		return sex;
	}

	public void setSex(String sex) {
		this.sex = sex;
	}*/
	
}

首先定義一個訊息的產生者Produce

public class Produce {

	public static void main(String[] args){
		
		Person person = new Person();
		person.setName("石仁闖");
		
		//增加sex後
		//person.setSex("男");
		//序列化 儲存到磁碟上

		SerializationUtils.writeObject(person);
		System.out.println(person);
	}
}

這裡執行了之後  person就被序列化到了E:/serializable.txt

然後定義一個消費者

public class Consumer {
	
	public static void main(String[] args) throws Exception{
		
		//反序列化
		Person p = (Person)SerializationUtils.readObject();
		System.out.println("反序列化得到的值"+p.getName());
		//System.out.println("反序列化得到的值"+p.getSex());
	}

}

好 執行到這裡是沒有什麼問題的;但是如果我們序列化與反序列化的時候參照的不是一個Person  會出現什麼情況  ;

比如我們在序列化之前person還是隻有一個屬性name   執行produce (序列化)之後;給person增加一個sex屬性(注意不要再執行produce (序列化)了);

增加了屬性之後   我們執行consumer(反序列化);會出現一個錯誤

書上說的是InvalidClassException錯誤;但是我親自執行報的是上面的錯誤;

為什麼會這樣呢?

原因是序列化與反序列化對應的類(person)版本不一致;JVM不能把資料流轉換為例項物件;

那JVM是怎麼判斷一個類的對應版本呢?

是通過SerivalVersionUID ,也就流識別符號,即類的版本定義

private static final long serialVersionUID = 1L;

UID可以隱式宣告和顯示宣告;隱式宣告是編譯器自動生成  基本上市唯一的;如果改變了類 ; 它是UID也會改變

在反序列化的時候 jvm會比較資料流中的UID與當前類(person)是否一致;如果一致說明類沒有改動;不一致說明是改動了,這是一個很好的效驗機制;

但是;有特殊情況;例如:我的類改變不大,我希望在反序列化的時候也能把它序列化出來。那怎麼辦呢?

既然是判斷UID是否一致,那我們讓他們的UID是一致的就可以了,顯示宣告UID 可以很好的解決這一問題;

建議12:避免用序列化類在建構函式中未不變數final賦值

private static final long serialVersionUID = 1L;
	
	//final 常量是一個直接量   在反序列化的時候就會重新計算  保持final物件的新舊統一  有利於程式碼業務邏輯統一
	
	public final String testFinal="序列化之前";
//	public final String testFinal="序列化之後";

序列化的時候給testFinal賦值是   序列化之前           

序列化完成之後   將testFinal改成    序列化之後   

然後執行反序列化

	//反序列化
		Person p = (Person)SerializationUtils.readObject();
		
		System.out.println("反序列化得到的值:"+p.testFinal);
輸出來的值是   序列化之前還是之後呢? 

輸出結果是:反序列化得到的值:序列化之後

為什麼呢?我們在序列化的時候 將testFinal序列化成了資料流存在了磁碟中  按理說反序列化的時候  得到的也是  序列化之前的值啊    為什麼變成了序列化之後?

這是因為序列化的基本規則之一:保持final新舊物件的相同。有利於程式碼業務邏輯的統一;也就是說,如果final是一個直接量  則反序列化時候final就會被重新計算;

final變數的另外一種賦值:建構函式賦值

public final String testFinal;

	public Person() {
		// TODO Auto-generated constructor stub
		testFinal="建構函式賦值  之前";
	}

序列化之後修改
testFinal="建構函式賦值  之後";
然後進行反序列化;猜想:這裡輸出應該會是   建構函式賦值  之後   吧?  因為規則一也是這樣的啊!

那到底輸出什麼呢?

反序列化得到的值:建構函式賦值  之前

為什麼還是之前  而不是改變之後的呢?  原因是另一個規則

反序列化時建構函式不被執行!

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

總結:反序列化在以下情況不能夠被重新賦值

1、通過建構函式為final變數賦值

2、通過方法未final變數賦值

3、final修飾的物件不是基本物件

建議14:使用序列化類的私有方法巧妙解決  部分屬性  持久化問題

......