成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Serializable:明明就一個(gè)空接口!為什么還要實(shí)現(xiàn)它?

開發(fā) 后端
對(duì)于 Java 的序列化,我一直停留在最淺顯的認(rèn)知上——把那個(gè)要序列化的類實(shí)現(xiàn) Serializbale 接口就可以了。我不愿意做更深入的研究,因?yàn)闀?huì)用就行了嘛。

[[318236]]

對(duì)于 Java 的序列化,我一直停留在最淺顯的認(rèn)知上——把那個(gè)要序列化的類實(shí)現(xiàn) Serializbale 接口就可以了。我不愿意做更深入的研究,因?yàn)闀?huì)用就行了嘛。

但隨著時(shí)間的推移,見到 Serializbale 的次數(shù)越來越多,我便對(duì)它產(chǎn)生了濃厚的興趣。是時(shí)候花點(diǎn)時(shí)間研究研究了。

01、先來點(diǎn)理論

Java 序列化是 JDK 1.1 時(shí)引入的一組開創(chuàng)性的特性,用于將 Java 對(duì)象轉(zhuǎn)換為字節(jié)數(shù)組,便于存儲(chǔ)或傳輸。此后,仍然可以將字節(jié)數(shù)組轉(zhuǎn)換回 Java 對(duì)象原有的狀態(tài)。

序列化的思想是“凍結(jié)”對(duì)象狀態(tài),然后寫到磁盤或者在網(wǎng)絡(luò)中傳輸;反序列化的思想是“解凍”對(duì)象狀態(tài),重新獲得可用的 Java 對(duì)象。

再來看看序列化 Serializbale 接口的定義: 

  1. public interface Serializable {  

明明就一個(gè)空的接口嘛,竟然能夠保證實(shí)現(xiàn)了它的“類的對(duì)象”被序列化和反序列化?

02、再來點(diǎn)實(shí)戰(zhàn)

在回答上述問題之前,我們先來創(chuàng)建一個(gè)類(只有兩個(gè)字段,和對(duì)應(yīng)的 getter/setter),用于序列化和反序列化。 

  1. class Wanger {  
  2.     private String name;  
  3.     private int age;  
  4.     public String getName() {  
  5.         return name;  
  6.     }  
  7.     public void setName(String name) {  
  8.         this.name = name;  
  9.     }  
  10.     public int getAge() {  
  11.         return age;  
  12.     }  
  13.     public void setAge(int age) {  
  14.         this.age = age;  
  15.     }  

再來創(chuàng)建一個(gè)測試類,通過 ObjectOutputStream 將“18 歲的王二”寫入到文件當(dāng)中,實(shí)際上就是一種序列化的過程;再通過 ObjectInputStream 將“18 歲的王二”從文件中讀出來,實(shí)際上就是一種反序列化的過程。 

  1. public class Test {  
  2.     public static void main(String[] args) {  
  3.       // 初始化  
  4.         Wanger wanger = new Wanger();  
  5.         wanger.setName("王二");  
  6.         wanger.setAge(18);  
  7.         System.out.println(wanger);  
  8.         // 把對(duì)象寫到文件中  
  9.         try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));){  
  10.             oos.writeObject(wanger);  
  11.         } catch (IOException e) {  
  12.             e.printStackTrace();  
  13.         }  
  14.         // 從文件中讀出對(duì)象  
  15.         try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));){  
  16.             Wanger wanger1 = (Wanger) ois.readObject();  
  17.             System.out.println(wanger1);  
  18.         } catch (IOException | ClassNotFoundException e) {  
  19.             e.printStackTrace();  
  20.         }  
  21.     }  

不過,由于 Wanger 沒有實(shí)現(xiàn) Serializbale 接口,所以在運(yùn)行測試類的時(shí)候會(huì)拋出異常,堆棧信息如下: 

  1. java.io.NotSerializableException: com.cmower.java_demo.xuliehua.Wanger  
  2.     at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)  
  3.     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)  
  4.     at com.cmower.java_demo.xuliehua.Test.main(Test.java:21) 

順著堆棧信息,我們來看一下 ObjectOutputStream 的 writeObject0() 方法。其部分源碼如下: 

  1. if (obj instanceof String) {  
  2.     writeString((String) obj, unshared);  
  3. } else if (cl.isArray()) {  
  4.     writeArray(obj, desc, unshared);  
  5. } else if (obj instanceof Enum) {  
  6.     writeEnum((Enum<?>) obj, desc, unshared);  
  7. } else if (obj instanceof Serializable) {  
  8.     writeOrdinaryObject(obj, desc, unshared);  
  9. } else {  
  10.     if (extendedDebugInfo) {  
  11.         throw new NotSerializableException(  
  12.             cl.getName() + "\n" + debugInfoStack.toString());  
  13.     } else {  
  14.         throw new NotSerializableException(cl.getName());  
  15.     }  

也就是說,ObjectOutputStream 在序列化的時(shí)候,會(huì)判斷被序列化的對(duì)象是哪一種類型,字符串?數(shù)組?枚舉?還是 Serializable,如果全都不是的話,拋出 NotSerializableException。

假如 Wanger 實(shí)現(xiàn)了 Serializable 接口,就可以序列化和反序列化了。 

  1. class Wanger implements Serializable{ 
  2.      private static final long serialVersionUID = -2095916884810199532L;  
  3.     private String name;  
  4.     private int age; 
  5.  

具體怎么序列化呢?

以 ObjectOutputStream 為例吧,它在序列化的時(shí)候會(huì)依次調(diào)用 writeObject()→writeObject0()→writeOrdinaryObject()→writeSerialData()→invokeWriteObject()→defaultWriteFields()。 

  1. private void defaultWriteFields(Object obj, ObjectStreamClass desc)  
  2.         throws IOException  
  3.     {  
  4.         Class<?> cl = desc.forClass();  
  5.         desc.checkDefaultSerialize();  
  6.         int primDataSize = desc.getPrimDataSize();  
  7.         desc.getPrimFieldValues(obj, primVals);  
  8.         bout.write(primVals, 0, primDataSize, false);  
  9.         ObjectStreamField[] fields = desc.getFields(false);  
  10.         Object[] objVals = new Object[desc.getNumObjFields()];  
  11.         int numPrimFields = fields.length - objVals.length;  
  12.         desc.getObjFieldValues(obj, objVals);  
  13.         for (int i = 0; i < objVals.length; i++) {  
  14.             try {  
  15.                 writeObject0(objVals[i],  
  16.                              fields[numPrimFields + i].isUnshared());  
  17.             }  
  18.         }  
  19.     } 

那怎么反序列化呢?

以 ObjectInputStream 為例,它在反序列化的時(shí)候會(huì)依次調(diào)用 readObject()→readObject0()→readOrdinaryObject()→readSerialData()→defaultReadFields()。 

  1. private void defaultWriteFields(Object obj, ObjectStreamClass desc)  
  2.         throws IOException  
  3.     {  
  4.         Class<?> cl = desc.forClass();  
  5.         desc.checkDefaultSerialize();  
  6.         int primDataSize = desc.getPrimDataSize();  
  7.         desc.getPrimFieldValues(obj, primVals);  
  8.         bout.write(primVals, 0, primDataSize, false);  
  9.         ObjectStreamField[] fields = desc.getFields(false);  
  10.         Object[] objVals = new Object[desc.getNumObjFields()];  
  11.         int numPrimFields = fields.length - objVals.length;  
  12.         desc.getObjFieldValues(obj, objVals);  
  13.         for (int i = 0; i < objVals.length; i++) {  
  14.             try {  
  15.                 writeObject0(objVals[i],  
  16.                              fields[numPrimFields + i].isUnshared());  
  17.             }  
  18.         }  
  19.     } 

我想看到這,你應(yīng)該會(huì)恍然大悟的“哦”一聲了。Serializable 接口之所以定義為空,是因?yàn)樗黄鸬搅艘粋€(gè)標(biāo)識(shí)的作用,告訴程序?qū)崿F(xiàn)了它的對(duì)象是可以被序列化的,但真正序列化和反序列化的操作并不需要它來完成。

03、再來點(diǎn)注意事項(xiàng)

開門見山的說吧,static 和 transient 修飾的字段是不會(huì)被序列化的。

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

首先,在 Wanger 類中增加兩個(gè)字段。 

  1. class Wanger implements Serializable {  
  2.     private static final long serialVersionUID = -2095916884810199532L;  
  3.     private String name;  
  4.     private int age;  
  5.     public static String pre = "沉默" 
  6.     transient String meizi = "王三" 
  7.     @Override  
  8.     public String toString() {  
  9.         return "Wanger{" + "name=" + name + ",age=" + age + ",pre=" + pre + ",meizi=" + meizi + "}";  
  10.     }  

其次,在測試類中打印序列化前和反序列化后的對(duì)象,并在序列化后和反序列化前改變 static 字段的值。具體代碼如下: 

  1. class Wanger implements Serializable {  
  2.     private static final long serialVersionUID = -2095916884810199532L;  
  3.     private String name;  
  4.     private int age;  
  5.     public static String pre = "沉默" 
  6.     transient String meizi = "王三" 
  7.     @Override  
  8.     public String toString() {  
  9.         return "Wanger{" + "name=" + name + ",age=" + age + ",pre=" + pre + ",meizi=" + meizi + "}";  
  10.     }  

從結(jié)果的對(duì)比當(dāng)中,我們可以發(fā)現(xiàn):

1)序列化前,pre 的值為“沉默”,序列化后,pre 的值修改為“不沉默”,反序列化后,pre 的值為“不沉默”,而不是序列化前的狀態(tài)“沉默”。

為什么呢?因?yàn)樾蛄谢4娴氖菍?duì)象的狀態(tài),而 static 修飾的字段屬于類的狀態(tài),因此可以證明序列化并不保存 static 修飾的字段。

2)序列化前,meizi 的值為“王三”,反序列化后,meizi 的值為 null,而不是序列化前的狀態(tài)“王三”。

為什么呢?transient 的中文字義為“臨時(shí)的”(論英語的重要性),它可以阻止字段被序列化到文件中,在被反序列化后,transient 字段的值被設(shè)為初始值,比如 int 型的初始值為 0,對(duì)象型的初始值為 null。

如果想要深究源碼的話,你可以在 ObjectStreamClass 中發(fā)現(xiàn)下面這樣的代碼: 

  1. private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {  
  2.     Field[] clclFields = cl.getDeclaredFields();  
  3.     ArrayList<ObjectStreamField> list = new ArrayList<>();  
  4.     int mask = Modifier.STATIC | Modifier.TRANSIENT;  
  5.     int size = list.size();  
  6.     return (size == 0) ? NO_FIELDS :  
  7.         list.toArray(new ObjectStreamField[size]);  

看到 Modifier.STATIC | Modifier.TRANSIENT,是不是感覺更好了呢?

04、再來點(diǎn)干貨

除了 Serializable 之外,Java 還提供了一個(gè)序列化接口 Externalizable(念起來有點(diǎn)拗口)。

兩個(gè)接口有什么不一樣的嗎?試一試就知道了。

首先,把 Wanger 類實(shí)現(xiàn)的接口  Serializable 替換為 Externalizable。 

  1. class Wanger implements Externalizable {  
  2.     private String name;  
  3.     private int age;  
  4.     public Wanger() {  
  5.     }  
  6.     public String getName() {  
  7.         return name;  
  8.     }  
  9.     @Override  
  10.     public String toString() {  
  11.         return "Wanger{" + "name=" + name + ",age=" + age + "}";  
  12.     }  
  13.     @Override  
  14.     public void writeExternal(ObjectOutput out) throws IOException {  
  15.     }  
  16.     @Override  
  17.     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {  
  18.     }  

實(shí)現(xiàn) Externalizable 接口的 Wanger 類和實(shí)現(xiàn) Serializable 接口的 Wanger 類有一些不同:

1)新增了一個(gè)無參的構(gòu)造方法。

使用 Externalizable 進(jìn)行反序列化的時(shí)候,會(huì)調(diào)用被序列化類的無參構(gòu)造方法去創(chuàng)建一個(gè)新的對(duì)象,然后再將被保存對(duì)象的字段值復(fù)制過去。否則的話,會(huì)拋出以下異常: 

  1. java.io.InvalidClassException: com.cmower.java_demo.xuliehua1.Wanger; no valid constructor  
  2.     at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)  
  3.     at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:790)  
  4.     at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1782)  
  5.     at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)  
  6.     at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)  
  7.     at com.cmower.java_demo.xuliehua1.Test.main(Test.java:27) 

2)新增了兩個(gè)方法 writeExternal() 和 readExternal(),實(shí)現(xiàn) Externalizable 接口所必須的。

然后,我們?cè)僭跍y試類中打印序列化前和反序列化后的對(duì)象。 

  1. // 初始化  
  2. Wanger wanger = new Wanger();  
  3. wanger.setName("王二");  
  4. wanger.setAge(18);  
  5. System.out.println(wanger);  
  6. // 把對(duì)象寫到文件中  
  7. try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));) {  
  8.     oos.writeObject(wanger);  
  9. } catch (IOException e) {  
  10.     e.printStackTrace();  
  11.  
  12. // 從文件中讀出對(duì)象  
  13. try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));) {  
  14.     Wanger wanger1 = (Wanger) ois.readObject();  
  15.     System.out.println(wanger1);  
  16. } catch (IOException | ClassNotFoundException e) {  
  17.     e.printStackTrace();  
  18.  
  19. // Wanger{name=王二,age=18 
  20. // Wanger{name=null,age=0

從輸出的結(jié)果看,反序列化后得到的對(duì)象字段都變成了默認(rèn)值,也就是說,序列化之前的對(duì)象狀態(tài)沒有被“凍結(jié)”下來。

為什么呢?因?yàn)槲覀儧]有為 Wanger 類重寫具體的 writeExternal() 和 readExternal() 方法。那該怎么重寫呢? 

  1. @Override  
  2. public void writeExternal(ObjectOutput out) throws IOException {  
  3.     out.writeObject(name);  
  4.     out.writeInt(age);  
  5.  
  6. @Override  
  7. public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {  
  8.     name = (String) in.readObject();  
  9.     age = in.readInt();  

1)調(diào)用 ObjectOutput 的 writeObject() 方法將字符串類型的 name 寫入到輸出流中;

2)調(diào)用 ObjectOutput 的 writeInt() 方法將整型的 age 寫入到輸出流中;

3)調(diào)用 ObjectInput 的 readObject() 方法將字符串類型的 name 讀入到輸入流中;

4)調(diào)用 ObjectInput 的 readInt() 方法將字符串類型的 age 讀入到輸入流中;

再運(yùn)行一次測試了類,你會(huì)發(fā)現(xiàn)對(duì)象可以正常地序列化和反序列化了。

序列化前:Wanger{name=王二,age=18}

序列化后:Wanger{name=王二,age=18}

05、再來點(diǎn)甜點(diǎn)

讓我先問問你吧,你知道 private static final long serialVersionUID = -2095916884810199532L; 這段代碼的作用嗎?

嗯……

serialVersionUID 被稱為序列化 ID,它是決定 Java 對(duì)象能否反序列化成功的重要因子。在反序列化時(shí),Java 虛擬機(jī)會(huì)把字節(jié)流中的 serialVersionUID 與被序列化類中的 serialVersionUID 進(jìn)行比較,如果相同則可以進(jìn)行反序列化,否則就會(huì)拋出序列化版本不一致的異常。

當(dāng)一個(gè)類實(shí)現(xiàn)了 Serializable 接口后,IDE 就會(huì)提醒該類最好產(chǎn)生一個(gè)序列化 ID,就像下面這樣:

1)添加一個(gè)默認(rèn)版本的序列化 ID: 

  1. private static final long serialVersionUID = 1L。 

2)添加一個(gè)隨機(jī)生成的不重復(fù)的序列化 ID。 

  1. private static final long serialVersionUID = -2095916884810199532L; 

3)添加 @SuppressWarnings 注解。 

  1. @SuppressWarnings("serial") 

怎么選擇呢?

首先,我們采用第二種辦法,在被序列化類中添加一個(gè)隨機(jī)生成的序列化 ID。 

  1. class Wanger implements Serializable {  
  2.     private static final long serialVersionUID = -2095916884810199532L;  
  3.     private String name;  
  4.     private int age;  
  5.     // 其他代碼忽略  

然后,序列化一個(gè) Wanger 對(duì)象到文件中。 

  1. // 初始化  
  2. Wanger wanger = new Wanger();  
  3. wanger.setName("王二");  
  4. wanger.setAge(18);  
  5. System.out.println(wanger);  
  6. // 把對(duì)象寫到文件中  
  7. try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));) {  
  8.     oos.writeObject(wanger);  
  9. } catch (IOException e) {  
  10.     e.printStackTrace();  

這時(shí)候,我們悄悄地把 Wanger 類的序列化 ID 偷梁換柱一下,嘿嘿。 

  1. // private static final long serialVersionUID = -2095916884810199532L;  
  2. private static final long serialVersionUID = -2095916884810199533L; 

好了,準(zhǔn)備反序列化吧。 

  1. try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));) {  
  2.     Wanger wanger = (Wanger) ois.readObject();  
  3.     System.out.println(wanger);  
  4. } catch (IOException | ClassNotFoundException e) {  
  5.     e.printStackTrace();  

哎呀,出錯(cuò)了。 

  1. java.io.InvalidClassException:  local class incompatible: stream classdesc   
  2. serialVersionUID = -2095916884810199532,  
  3. local class serialVersionUID = -2095916884810199533  
  4.     at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)  
  5.     at com.cmower.java_demo.xuliehua1.Test.main(Test.java:27) 

異常堆棧信息里面告訴我們,從持久化文件里面讀取到的序列化 ID 和本地的序列化 ID 不一致,無法反序列化。

那假如我們采用第三種方法,為 Wanger 類添加個(gè) @SuppressWarnings("serial") 注解呢? 

  1. @SuppressWarnings("serial")  
  2. class Wanger3 implements Serializable {  
  3. // 省略其他代碼  

好了,再來一次反序列化吧??上б廊粓?bào)錯(cuò)。 

  1. java.io.InvalidClassException:  local class incompatible: stream classdesc   
  2. serialVersionUID = -2095916884810199532,   
  3. local class serialVersionUID = -3818877437117647968  
  4.     at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)  
  5.     at com.cmower.java_demo.xuliehua1.Test.main(Test.java:27) 

異常堆棧信息里面告訴我們,本地的序列化 ID 為 -3818877437117647968,和持久化文件里面讀取到的序列化 ID 仍然不一致,無法反序列化。這說明什么呢?使用 @SuppressWarnings("serial") 注解時(shí),該注解會(huì)為被序列化類自動(dòng)生成一個(gè)隨機(jī)的序列化 ID。

由此可以證明,Java 虛擬機(jī)是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,還有一個(gè)非常重要的因素就是序列化 ID 是否一致。

也就是說,如果沒有特殊需求,采用默認(rèn)的序列化 ID(1L)就可以,這樣可以確保代碼一致時(shí)反序列化成功。 

  1. class Wanger implements Serializable {  
  2.     private static final long serialVersionUID = 1L 
  3. // 省略其他代碼  

06、再來點(diǎn)總結(jié)

寫這篇文章之前,我真沒想到:“空空其身”的Serializable 竟然有這么多可以研究的內(nèi)容!

寫完這篇文章之后,我不由得想起理科狀元曹林菁說說過的一句話:“在學(xué)習(xí)中再小的問題也不放過,每個(gè)知識(shí)點(diǎn)都要總結(jié)”——說得真真真真的對(duì)??! 

責(zé)任編輯:龐桂玉 來源: Java后端技術(shù)
相關(guān)推薦

2021-07-19 09:00:24

微軟Windows 11Windows

2024-10-12 15:10:23

2011-06-01 15:18:43

Serializabl

2019-05-14 09:05:16

SerializablJava對(duì)象

2021-11-17 08:26:22

空類EBO技術(shù)

2014-08-21 10:05:14

ZMapTCPIP

2021-03-02 22:10:10

Java互聯(lián)網(wǎng)語言

2022-06-07 08:39:35

RPCHTTP

2021-04-16 23:28:11

Java語言IT

2020-10-29 09:19:11

索引查詢存儲(chǔ)

2022-04-29 08:00:06

Linux目錄網(wǎng)絡(luò)

2016-10-27 11:11:12

頭條

2023-03-26 00:04:14

2022-06-10 13:03:44

接口重試while

2022-08-04 08:22:49

MySQL索引

2023-11-07 08:00:00

Kubernetes

2018-03-22 14:47:13

容器開發(fā)人員筆記本

2021-12-24 10:04:57

漏洞阿里云Log4Shell

2012-09-03 09:52:39

虛擬化

2019-08-05 14:23:43

DockerKubernetes容器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 国产一区 | 国产精品成人在线播放 | 狼色网| 欧美5区| 欧美二级 | 婷婷福利视频导航 | 国产精品一区二区免费 | 亚洲精品视频一区 | 欧美日韩高清在线观看 | 成人在线精品视频 | 日韩不卡一区二区 | 亚洲 欧美 另类 综合 偷拍 | 91精品国产高清一区二区三区 | 精品一区二区在线观看 | 91av导航 | 久久国产美女视频 | 91精品久久久久 | 国产一区二区欧美 | 一级毛片视频在线观看 | 国产黄色网址在线观看 | 国产精品久久久乱弄 | 日韩一区二区三区在线观看 | 精品国产91乱码一区二区三区 | 久热9| 欧美日韩综合一区 | 国产精品免费大片 | 久久天堂网| 国产在线观看不卡一区二区三区 | 一区观看 | 日韩精品一区二区三区老鸭窝 | 久久精品无码一区二区三区 | 久久视频免费看 | 欧美日韩久久精品 | 国产精品视频一二三区 | 亚洲第1页 | 日韩福利在线 | 在线欧美日韩 | 国产在线一区观看 | 精品欧美一区二区在线观看 | 国产高清视频 | 91av在线电影 |