1. 程式人生 > >java序列化的版本管理

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關鍵字