欄位為空_P8帶你實戰Java Serializable:證明它就是就一個空的介面
技術標籤:欄位為空
一、 目錄
- 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 +