java序列化的版本管理
本文是針對java序列化的版本管理進行闡述的,請大家先看個例子,
一個用於網路傳輸的實體類FInterfaceObject,程式中是這樣使用的:
public class SeriaTest
{
public static void main(String[] args)
{
String fileUrl = "./example";
FInterfaceObject fIObjOutput =FInterfaceObject.staticFinalFIObj;
write(fileUrl,fIObjOutput);
FInterfaceObject obj =(FInterfaceObject)read(fileUrl);
System.out.println(obj);
}
public static void write(String fileUrl,FInterfaceObjectobj)
{
ObjectOutputStream out = null;
try
{
out = new ObjectOutputStream(newFileOutputStream(fileUrl));
out.writeObject(obj);
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
if(out != null)
{
try
{
out.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
}
public static FInterfaceObject read(StringfileUrl)
{
ObjectInputStream in = null;
FInterfaceObject fIObjInput = null;
try
{
in = new ObjectInputStream(newFileInputStream(fileUrl));
fIObjInput = (FInterfaceObject)in.readObject();
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
if(in != null)
{
try
{
in.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
return fIObjInput;
}
}
類FInterfaceObject的定義如下:
public class FInterfaceObjectimplements Serializable
{
public static finalFInterfaceObjectstaticFinalFIObj = newFInterfaceObject(1);
private int id = 0;
private FInterfaceObject (int value)
{
id = value;
}
public String toString()
{
return"id: "+String.valueOf(id);
}
}
此時,執行程式SeriaTest.java,輸出列印資訊為:id: 1
之後,由於需求變化,需要給FInterfaceObject新增個域:String name,類定義變為:
public class FInterfaceObject implements Serializable
{
public static final FInterfaceObject staticFinalFIObj = newFInterfaceObject(1);
private int id = 0;
private Stringname = "object1";
private FInterfaceObject(int value)
{
id = value;
}
public String toString()
{
return"id: "+String.valueOf(id) +"name: "+name;
}
}
此時,將程式SeriaTest.java的main()方法改為如下,只寫出,且寫出到example2中,後面會用到該檔案,暫且放下。
public static void main(String[] args)
{
String fileUrl = "./example2";
FInterfaceObject fIObjOutput = FInterfaceObject.staticFinalFIObj;
write(fileUrl,fIObjOutput);
}
現在,再次修改程式SeriaTest.java的main()方法為只讀入example檔案,去掉寫出的兩行程式碼:
public static void main(String[] args)
{
String fileUrl = "./example";
FInterfaceObject obj =(FInterfaceObject)read(fileUrl);
System.out.println(obj);
}
之後執行程式SeriaTest.java,重新讀入之前的輸出檔案example,大家猜猜會列印什麼呢?下面是執行結果:
null
java.io.InvalidClassException: practice.FInterfaceObject; local class incompatible:stream classdesc serialVersionUID = 8436122587265072399, local classserialVersionUID = 2044314194551933020
at java.io.ObjectStreamClass.initNonProxy(UnknownSource)
……
為什麼讀出來的物件是null?這個異常又是什麼意思?
原因就在於java的流機制,拒絕讀入序列化版本不同的物件,異常資訊表明:流讀入的物件類序列化版本為8436122587265072399,而本地程式中該類的序列化版本為:2044314194551933020,兩者不一致,因此流拒絕讀入而拋java.io.InvalidClassException異常。拋異常後,read方法返回null,因此打印出來是null.
那如何解決這個問題呢?
為了向jvm表示,新類相容之前版本的類,需要將之前類的序列化版本UID寫入新類,做為新類的static final 域。即在類FInterfaceObject中,增加下面一行:
private static final longserialVersionUID = 8436122587265072399L;
增加這行後,再執行程式SeriaTest.java,程式輸出:
id: 1 name:null.
example.txt檔案中的物件是沒有域name的,但java流機制可以相容處理,對流中物件少於本地類中的屬性,根據屬性型別的不同,取其對應的預設值(如果是物件則是null,數字則是0,如果是boolean則是false)。若流中物件域多於本地類,則忽略這些域。
需求繼續變化,又需要給類FInterfaceObject中,增加一個域:int age,類定義變為:
public class FInterfaceObject implements Serializable
{
private static final long serialVersionUID = 8436122587265072399L;
public static final FInterfaceObject staticFinalFIObj = newFInterfaceObject(1);
private int id = 0;
private Stringname = "object1";
private int age = 1;
private FInterfaceObject(int value)
{
id = value;
}
public String toString()
{
return"id:"+String.valueOf(id) +" name: "+name+" age: "+age;
}
}
此時執行程式SeriaTest.java,如下:
public static void main(String[] args)
{
String fileUrl = "./example";
FInterfaceObject obj =(FInterfaceObject)read(fileUrl);
System.out.println(obj);
}
,程式輸出:
id: 1 name: null age: 0.
之後,將程式SeriaTest.java修改為讀入檔案"./example2",程式輸出如下:
java.io.InvalidClassException:practice.FInterfaceObject; local class incompatible: stream classdescserialVersionUID = 2044314194551933020, local class serialVersionUID =8436122587265072399
atjava.io.ObjectStreamClass.initNonProxy(Unknown Source)
……
null
異常資訊表明,本地類序列化版本與流讀入物件的序列化版本不一致,可以通過將serialVersionUID改為2044314194551933020L成功讀入檔案"./example2",但若再讀入"./example",又會因序列化版本不一致而報該異常。
若碰到此種情況,就不能通過僅僅修改程式碼解決問題了。就需要在業務層面上,規避該問題。
因此,一個用於持久化或者網路傳輸的類,其序列化版本號最好在一開始就顯示的在類中宣告,這樣,即使後面類發生多次變化,使用的serialVersionUID都相同,就不會出現序列化版本號跟隨類變化,導致流拒絕讀入不同版本的物件的現象。Eclipse中,對實現了介面Serializable的類,若未顯示宣告serialVersionUID,會顯示編譯警告提示。
以下是java序列化的思考點:
1、 靜態資料域的序列化(基本型別和Object物件)
2、 讀的舊類序列化後能呼叫新的方法嗎?
3、 使用序列化機制clone
4、 readResolve()方法
5、 Externalizable介面,實現自己的儲存恢復物件資料的方法
6、 修改預設的序列化方法
7、 transient關鍵字