1. 程式人生 > 其它 >欄位為空_P8帶你實戰Java Serializable:證明它就是就一個空的介面

欄位為空_P8帶你實戰Java Serializable:證明它就是就一個空的介面

技術標籤:欄位為空

c85c272c302e21f5cbe1316f59468968.png

一、 目錄

  • 01、理論
  • 02、實戰
  • 03、注意事項
  • 04、乾貨
  • 05、甜點
  • 06、總結

前言:

對於 Java 的序列化,我一直停留在最淺顯的認知上——把那個要序列化的類實現 Serializbale 介面就可以了。我不願意做更深入的研究,因為會用就行了嘛。

但隨著時間的推移,見到 Serializbale 的次數越來越多,我便對它產生了濃厚的興趣。是時候花點時間研究研究了。

01、先來點理論

Java 序列化是 JDK 1.1 時引入的一組開創性的特性,用於將 Java 物件轉換為位元組陣列,便於儲存或傳輸。此後,仍然可以將位元組陣列轉換回 Java 物件原有的狀態。

序列化的思想是“凍結”物件狀態,然後寫到磁碟或者在網路中傳輸;反序列化的思想是“解凍”物件狀態,重新獲得可用的 Java 物件。

再來看看序列化 Serializbale 介面的定義:

public interface Serializable {}

明明就一個空的介面嘛,竟然能夠保證實現了它的“類的物件”被序列化和反序列化?

02、再來點實戰

在回答上述問題之前,我們先來建立一個類(只有兩個欄位,和對應的 getter/setter),用於序列化和反序列化。

class Wanger { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}

再來建立一個測試類,通過 ObjectOutputStream 將“18 歲的王二”寫入到檔案當中,實際上就是一種序列化的過程;再通過 ObjectInputStream 將“18 歲的王二”從檔案中讀出來,實際上就是一種反序列化的過程。

public class Test { public static void main(String[] args) { // 初始化 Wanger wanger = new Wanger(); wanger.setName("王二"); wanger.setAge(18); System.out.println(wanger); // 把物件寫到檔案中 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));){ oos.writeObject(wanger); } catch (IOException e) { e.printStackTrace(); } // 從檔案中讀出物件 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));){ Wanger wanger1 = (Wanger) ois.readObject(); System.out.println(wanger1); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } }}

不過,由於 Wanger 沒有實現 Serializbale 介面,所以在執行測試類的時候會丟擲異常,堆疊資訊如下:

java.io.NotSerializableException: com.cmower.java_demo.xuliehua.Wangerat java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)at com.cmower.java_demo.xuliehua.Test.main(Test.java:21)

順著堆疊資訊,我們來看一下 ObjectOutputStream 的 writeObject0() 方法。其部分原始碼如下:

if (obj instanceof String) { writeString((String) obj, unshared);} else if (cl.isArray()) { writeArray(obj, desc, unshared);} else if (obj instanceof Enum) { writeEnum((Enum>) obj, desc, unshared);} else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared);} else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); }}

也就是說,ObjectOutputStream 在序列化的時候,會判斷被序列化的物件是哪一種型別,字串?陣列?列舉?還是 Serializable,如果全都不是的話,丟擲 NotSerializableException。

假如 Wanger 實現了 Serializable 介面,就可以序列化和反序列化了。

class Wanger implements Serializable{ private static final long serialVersionUID = -2095916884810199532L;  private String name; private int age;}

具體怎麼序列化呢?

以 ObjectOutputStream 為例吧,它在序列化的時候會依次呼叫 writeObject()→writeObject0()→writeOrdinaryObject()→writeSerialData()→invokeWriteObject()→defaultWriteFields()。

private void defaultWriteFields(Object obj, ObjectStreamClass desc) throws IOException { Class> cl = desc.forClass(); desc.checkDefaultSerialize(); int primDataSize = desc.getPrimDataSize(); desc.getPrimFieldValues(obj, primVals); bout.write(primVals, 0, primDataSize, false); ObjectStreamField[] fields = desc.getFields(false); Object[] objVals = new Object[desc.getNumObjFields()]; int numPrimFields = fields.length - objVals.length; desc.getObjFieldValues(obj, objVals); for (int i = 0; i < objVals.length; i++) {  try { writeObject0(objVals[i], fields[numPrimFields + i].isUnshared()); } } }

那怎麼反序列化呢?

以 ObjectInputStream 為例,它在反序列化的時候會依次呼叫 readObject()→readObject0()→readOrdinaryObject()→readSerialData()→defaultReadFields()。

private void defaultWriteFields(Object obj, ObjectStreamClass desc) throws IOException { Class> cl = desc.forClass(); desc.checkDefaultSerialize(); int primDataSize = desc.getPrimDataSize(); desc.getPrimFieldValues(obj, primVals); bout.write(primVals, 0, primDataSize, false); ObjectStreamField[] fields = desc.getFields(false); Object[] objVals = new Object[desc.getNumObjFields()]; int numPrimFields = fields.length - objVals.length; desc.getObjFieldValues(obj, objVals); for (int i = 0; i < objVals.length; i++) {  try { writeObject0(objVals[i], fields[numPrimFields + i].isUnshared()); } } }

我想看到這,你應該會恍然大悟的“哦”一聲了。Serializable 介面之所以定義為空,是因為它只起到了一個標識的作用,告訴程式實現了它的物件是可以被序列化的,但真正序列化和反序列化的操作並不需要它來完成。

03、再來點注意事項

開門見山的說吧,static 和 transient 修飾的欄位是不會被序列化的。

為什麼呢?我們先來證明,再來解釋原因。

首先,在 Wanger 類中增加兩個欄位。

class Wanger implements Serializable { private static final long serialVersionUID = -2095916884810199532L; private String name; private int age; public static String pre = "沉默"; transient String meizi = "王三"; @Override public String toString() { return "Wanger{" + "name=" + name +