日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java Serializable:明明就一个空的接口嘛

發布時間:2024/1/23 java 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java Serializable:明明就一个空的接口嘛 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

對于 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.Wanger
?? ?at 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() + "\n" + 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 + ",age=" + age + ",pre=" + pre + ",meizi=" + meizi + "}";
? ? }
}

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

// 初始化
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();
? ? }
? ?
? ? // 改變 static 字段的值
Wanger.pre ="不沉默";

// 從文件中讀出對象
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{name=王二,age=18,pre=沉默,meizi=王三}
// Wanger{name=王二,age=18,pre=不沉默,meizi=null}


從結果的對比當中,我們可以發現:

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

為什么呢?因為序列化保存的是對象的狀態,而 static 修飾的字段屬于類的狀態,因此可以證明序列化并不保存 static 修飾的字段。

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

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

如果想要深究源碼的話,你可以在 ObjectStreamClass 中發現下面這樣的代碼:

private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
? ? Field[] clFields = cl.getDeclaredFields();
? ? ArrayList<ObjectStreamField> list = new ArrayList<>();
? ? int mask = Modifier.STATIC | Modifier.TRANSIENT;

? ? int size = list.size();
? ? return (size == 0) ? NO_FIELDS :
? ? ? ? list.toArray(new ObjectStreamField[size]);
}

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

04、再來點干貨
除了 Serializable 之外,Java 還提供了一個序列化接口 Externalizable(念起來有點拗口)。

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

首先,把 Wanger 類實現的接口 Serializable 替換為 Externalizable。

class Wanger implements Externalizable {
?? ?private String name;
?? ?private int age;

?? ?public Wanger() {

?? ?}

?? ?public String getName() {
?? ??? ?return name;
?? ?}

?? ?
?? ?@Override
?? ?public String toString() {
?? ??? ?return "Wanger{" + "name=" + name + ",age=" + age + "}";
?? ?}

?? ?@Override
?? ?public void writeExternal(ObjectOutput out) throws IOException {

?? ?}

?? ?@Override
?? ?public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

?? ?}

}

實現 Externalizable 接口的 Wanger 類和實現 Serializable 接口的 Wanger 類有一些不同:

1)新增了一個無參的構造方法。

使用 Externalizable 進行反序列化的時候,會調用被序列化類的無參構造方法去創建一個新的對象,然后再將被保存對象的字段值復制過去。否則的話,會拋出以下異常:

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

2)新增了兩個方法 writeExternal() 和 readExternal(),實現 Externalizable 接口所必須的。

然后,我們再在測試類中打印序列化前和反序列化后的對象。

// 初始化
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{name=王二,age=18}
// Wanger{name=null,age=0}

從輸出的結果看,反序列化后得到的對象字段都變成了默認值,也就是說,序列化之前的對象狀態沒有被“凍結”下來。

為什么呢?因為我們沒有為 Wanger 類重寫具體的 writeExternal() 和 readExternal() 方法。那該怎么重寫呢?

@Override
public void writeExternal(ObjectOutput out) throws IOException {
?? ?out.writeObject(name);
?? ?out.writeInt(age);
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
?? ?name = (String) in.readObject();
?? ?age = in.readInt();
}

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

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

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

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

再運行一次測試了類,你會發現對象可以正常地序列化和反序列化了。

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

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

嗯…

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

當一個類實現了 Serializable 接口后,IDE 就會提醒該類最好產生一個序列化 ID,就像下面這樣:

1)添加一個默認版本的序列化 ID:

private static final long serialVersionUID = 1L。

2)添加一個隨機生成的不重復的序列化 ID。

private static final long serialVersionUID = -2095916884810199532L;

3)添加 @SuppressWarnings 注解。

@SuppressWarnings("serial")

怎么選擇呢?

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

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

?? ?// 其他代碼忽略
}

然后,序列化一個 Wanger 對象到文件中。

// 初始化
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();
}

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

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

好了,準備反序列化吧。

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

哎呀,出錯了。

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

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

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

@SuppressWarnings("serial")
class Wanger implements Serializable {
// 省略其他代碼
}

好了,再來一次反序列化吧??上б廊粓箦e。

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

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

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

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

class Wanger implements Serializable {
?? ?private static final long serialVersionUID = 1L;
// 省略其他代碼
}
---------------------?
作者:沉默王二?
來源:CSDN?
原文:https://blog.csdn.net/qing_gee/article/details/93176705?
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

總結

以上是生活随笔為你收集整理的Java Serializable:明明就一个空的接口嘛的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 亚洲欧美第一视频 | 最近更新中文字幕 | 99在线精品视频 | 国产精品久久久久不卡 | 大尺度做爰呻吟62集 | 欧美一级特黄aa大片 | 大毛片| 成年人天堂 | 一区二区视频国产 | 亚洲理论在线观看 | 亚洲av成人无码一二三在线观看 | 国产精品自慰网站 | 成人免费在线播放 | 男人天堂成人 | 婷婷伊人综合中文字幕 | 免费黄色在线看 | 白浆四溢 | 天天干网站 | 欧美色图日韩 | 四虎精品永久在线 | 国产又粗又黄视频 | 成av人片一区二区三区久久 | 一区二区三区成人 | 91在线免费视频观看 | 免费观看视频一区二区 | 亚洲一区欧美二区 | 高潮白浆女日韩av免费看 | 在线观看的黄网 | 亚洲在线不卡 | 国产精品123区 | 韩日三级视频 | 成人国产三级 | 中文字幕av高清片 | 国产美女视频网站 | 91啪在线观看 | 亚洲高清久久久 | 欧美三级电影在线观看 | 青青操91 | 永久黄网站| 刘亦菲久久免费一区二区 | 国产麻豆午夜三级精品 | 麻豆91精品 | 欧美视频自拍偷拍 | 亚洲国产中文字幕在线 | 亚洲熟女少妇一区 | 精品国产96亚洲一区二区三区 | 一级视频在线播放 | www.色就是色.com| 超碰在| 自拍视频一区 | 欧美成年人视频 | 日本免费不卡一区二区 | 91超薄肉色丝袜交足高跟凉鞋 | 天天拍天天射 | 性感美女被草 | 精品一久久 | 噜噜色综合| 91免费播放 | 夜夜操夜夜| 亚洲免费一区 | 亚洲国产专区 | 一女被多男玩喷潮视频 | 国产精品乱子伦 | 一本加勒比北条麻妃 | www.com操| 性――交――性――乱a | 一级片麻豆 | 久久免费视频3 | 久久久ww | 久久久久久视 | 中文字幕免费一区二区 | 青娱乐在线免费视频 | 亚洲精品激情视频 | 国模视频一区二区 | 久久无码视频网站 | h片在线免费看 | av网站地址 | 日本丰满熟妇hd | 一级黄色录像大片 | 日批在线 | 日韩av区 | 免费在线亚洲 | 国产日日操| 亚洲熟女www一区二区三区 | 欧美黑人三级 | 美女脱光衣服让男人捅 | 熟睡侵犯の奶水授乳在线 | 四虎永久在线视频 | 四虎永久在线精品免费网址 | 欧美精品999 | 欧美日韩不卡一区 | 亚洲色图激情小说 | 懂色av懂色av粉嫩av分享吧 | 黄色网免费 | 免费毛片一区二区三区 | 美女极度色诱图片www视频 | 中文字幕一区二区视频 | av集中营 | 天天躁日日躁狠狠躁av |