1. 程式人生 > 程式設計 >Java物件Serializable介面實現詳解

Java物件Serializable介面實現詳解

這篇文章主要介紹了Java物件Serializable介面實現詳解,文中通過示例程式碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

導讀

最近這段時間一直在忙著編寫Java業務程式碼,麻木地搬著Ctrl-C、Ctrl-V的磚,在不知道重複了多少次定義Java實體物件時“implements Serializable”的C/V大法後,腦海中突然冒出一個思維(A):問了自己一句“Java實體物件為什麼一定要實現Serializable介面呢?”,關於這個問題,腦海中的另一個思維(B)立馬給出了回覆“居然問這麼幼稚和基礎的問題,實現Serilizable介面是為了序列化啊!”,思維(A):“哦,好吧!然而,然後呢?”

此時思維(B)陷入了沉默,突然感覺自己有點淺薄了,好像寫了這麼多年Java還真是沒有太關注過Serializable這個介面!為什麼一定要實現Serializable介面?它的底層原理是什麼?為什麼一定要序列化,序列化又是什麼?關於這些問題,不知道各位讀者朋友有沒有過類似的問題,如果有那麼我們就在這篇文章中一起尋找答案吧!當然,如果你對這些問題都很清楚,也歡迎表達看法!

Serializable介面概述

Serializable是java.io包中定義的、用於實現Java類的序列化操作而提供的一個語義級別的介面。Serializable序列化介面沒有任何方法或者欄位,只是用於標識可序列化的語義。實現了Serializable介面的類可以被ObjectOutputStream轉換為位元組流,同時也可以通過ObjectInputStream再將其解析為物件。例如,我們可以將序列化物件寫入檔案後,再次從檔案中讀取它並反序列化成物件,也就是說,可以使用表示物件及其資料的型別資訊和位元組在記憶體中重新建立物件。

而這一點對於面向物件的程式語言來說是非常重要的,因為無論什麼程式語言,其底層涉及IO操作的部分還是由作業系統其幫其完成的,而底層IO操作都是以位元組流的方式進行的,所以寫操作都涉及將程式語言資料型別轉換為位元組流,而讀操作則又涉及將位元組流轉化為程式語言型別的特定資料型別。而Java作為一門面向物件的程式語言,物件作為其主要資料的型別載體,為了完成物件資料的讀寫操作,也就需要一種方式來讓JVM知道在進行IO操作時如何將物件資料轉換為位元組流,以及如何將位元組流資料轉換為特定的物件,而Serializable介面就承擔了這樣一個角色。

下面我們可以通過例子來實現將序列化的物件儲存到檔案,然後再將其從檔案中反序列化為物件,程式碼示例如下:

先定義一個序列化物件User:

public class User implements Serializable {
  private static final long serialVersionUID = 1L;

  private String userId;
  private String userName;

  public User(String userId,String userName) {
    this.userId = userId;
    this.userName = userName;
  }
}

然後我們編寫測試類,來對該物件進行讀寫操作,我們先測試將該物件寫入一個檔案:

public class SerializableTest {

  /**
   * 將User物件作為文字寫入磁碟
   */
  public static void writeObj() {
    User user = new User("1001","Joe");
    try {
      ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/Users/guanliyuan/user.txt"));
      objectOutputStream.writeObject(user);
      objectOutputStream.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public static void main(String args[]) {
    writeObj();
  }
}

執行上述程式碼,我們就將User物件及其攜帶的資料寫入了文字user.txt中,我們可以看下user.txt中儲存的資料此時是個什麼格式:

我們看到物件資料以二進位制文字的方式被持久化到了磁碟檔案中。在進行反序列化測試之前,我們可以嘗試下將User實現Serializable介面的程式碼部分去掉,看看此時寫操作是否還能成功,結果如下:

java.io.NotSerializableException: cn.wudimanong.serializable.User
  at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
  at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
  at cn.wudimanong.serializable.SerializableTest.writeObj(SerializableTest.java:19)
  at cn.wudimanong.serializable.SerializableTest.main(SerializableTest.java:27)

結果不出所料,果然是不可以的,丟擲了NotSerializableException異常,提示非可序列化異常,也就是說沒有實現Serializable介面的物件是無法通過IO操作持久化的。

接下來,我們繼續編寫測試程式碼,嘗試將之前持久化寫入user.txt檔案的物件資料再次轉化為Java物件,程式碼如下:

public class SerializableTest {
  /**
   * 將類從文字中提取並賦值給記憶體中的類
   */
  public static void readObj() {
    try {
      ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("/Users/guanliyuan/user.txt"));
      try {
        Object object = objectInputStream.readObject();
        User user = (User) object;
        System.out.println(user);
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public static void main(String args[]) {
    readObj();
  }
}

通過反序列化操作,可以再次將持久化的物件位元組流資料通過IO轉化為Java物件,結果如下:

cn.wudimanong.serializable.User@6f496d9f

此時,如果我們再次嘗試將User實現Serializable介面的程式碼部分去掉,發現也無法再文字轉換為序列化物件,報錯資訊為:

ava.io.InvalidClassException: cn.wudimanong.serializable.User; class invalid for deserialization
  at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:157)
  at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:862)
  at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2038)
  at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568)
  at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428)
  at cn.wudimanong.serializable.SerializableTest.readObj(SerializableTest.java:31)
  at cn.wudimanong.serializable.SerializableTest.main(SerializableTest.jav

提示非法型別轉換異常,說明在Java中如何要實現物件的IO讀寫操作,都必須實現Serializable介面,否則程式碼就會報錯!

序列化&反序列化

通過上面的闡述和示例,相信大家對Serializable介面的作用是有了比較具體的體會了,接下來我們上層到理論層面,看下到底什麼是序列化/反序列化。序列化是指把物件轉換為位元組序列的過程,我們稱之為物件的序列化,就是把記憶體中的這些物件變成一連串的位元組(bytes)描述的過程。

而反序列化則相反,就是把持久化的位元組檔案資料恢復為物件的過程。那麼什麼情況下需要序列化呢?大概有這樣兩類比較常見的場景:1)、需要把記憶體中的物件狀態資料儲存到一個檔案或者資料庫中的時候,這個場景是比較常見的,例如我們利用mybatis框架編寫持久層insert物件資料到資料庫中時;2)、網路通訊時需要用套接字在網路中傳送物件時,如我們使用RPC協議進行網路通訊時;

關於serialVersionUID

對於JVM來說,要進行持久化的類必須要有一個標記,只有持有這個標記JVM才允許類建立的物件可以通過其IO系統轉換為位元組資料,從而實現持久化,而這個標記就是Serializable介面。而在反序列化的過程中則需要使用serialVersionUID來確定由那個類來載入這個物件,所以我們在實現Serializable介面的時候,一般還會要去儘量顯示地定義serialVersionUID,如:

private static final long serialVersionUID = 1L;

在反序列化的過程中,如果接收方為物件載入了一個類,如果該物件的serialVersionUID與對應持久化時的類不同,那麼反序列化的過程中將會導致InvalidClassException異常。例如,在之前反序列化的例子中,我們故意將User類的serialVersionUID改為2L,如:

private static final long serialVersionUID = 2L;

那麼此時,在反序例化時就會導致異常,如下:

java.io.InvalidClassException: cn.wudimanong.serializable.User; local class incompatible: stream classdesc serialVersionUID = 1,local class serialVersionUID = 2
  at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
  at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1880)
  at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1746)
  at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2037)
  at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568)
  at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428)
  at cn.wudimanong.serializable.SerializableTest.readObj(SerializableTest.java:31)
  at cn.wudimanong.serializable.SerializableTest.main(SerializableTest.java:44)

如果我們在序列化中沒有顯示地宣告serialVersionUID,則序列化執行時將會根據該類的各個方面計算該類預設的serialVersionUID值。但是,Java官方強烈建議所有要序列化的類都顯示地宣告serialVersionUID欄位,因為如果高度依賴於JVM預設生成serialVersionUID,可能會導致其與編譯器的實現細節耦合,這樣可能會導致在反序列化的過程中發生意外的InvalidClassException異常。

因此,為了保證跨不同Java編譯器實現的serialVersionUID值的一致,實現Serializable介面的必須顯示地宣告serialVersionUID欄位。

此外serialVersionUID欄位地宣告要儘可能使用private關鍵字修飾,這是因為該欄位的宣告只適用於宣告的類,該欄位作為成員變數被子類繼承是沒有用處的!有個特殊的地方需要注意的是,陣列類是不能顯示地宣告serialVersionUID的,因為它們始終具有預設計算的值,不過陣列類反序列化過程中也是放棄了匹配serialVersionUID值的要求。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。