怎么并線(三條電線怎么并線)
808
2022-05-28
我們都知道,新建一個對象的時候實現 Serializeable 接口,但為什么要這么做?什么時候這樣子做?這樣子做會不會出現幺蛾子?阿粉一個三連差點把自己都問懵逼了……
那接下來,大家就和阿粉一起簡單了解一下這個知識點吧……
序
序列化的定義是:將一個對象編碼成一個字節流(I/O);而與之相反的操作被稱為反序列化。
序列化的目的是為了方便數據的傳遞以及存儲到磁盤上(把一個Java對象寫入到硬盤或者傳輸到網路上面的其它計算機,這時我們就需要將對象轉換成字節流才能進行網絡傳輸。對于這種通用的操作,就出現了序列化來統一這些格式)。
簡單來說序列化就是一種用來處理對象流的機制。將對象轉化成字節序列后可以保存在磁盤上,或通過網絡傳輸,以達到以后恢復成原來的對象。序列化機制使得對象可以脫離程序的運行而獨立存在。
使用場景:所有可在網絡上傳輸的對象都必須是可序列化的,比如RMI(remote method invoke,即遠程方法調用),傳入的參數或返回的對象都是可序列化的,否則會出錯;所有需要保存到磁盤的java對象都必須是可序列化的。比如 Redis 將對象當做字符串存儲的時候,如果對象實現了序列化,則只需要將對象直接存儲即可(java會自動將對象轉換成序列化后的字節流);否則需要自己將對象轉換成 json 字符串存儲,不過 json 字符串相對更加節省內存空間一些。
通常建議:程序創建的每個JavaBean類都實現 Serializeable 接口。但是實現 Serializeable 接口也需要小心謹慎。正如《Effective Java》中第 74 條提到的那樣:
如何實現序列化
在 Java 中,只要一個類實現了 java.io.Serializable 接口,它就可以被序列化(枚舉類也可以被序列化)。
例如:每個枚舉類型都會默認繼承類java.lang.Enum,而Enum類實現了Serializable接口,所以枚舉類型對象都是默認可以被序列化的。
//?DeletedEnum?類,表示刪除狀態
@Getter
@AllArgsConstructor
public?enum?DeletedEnum?{
NO_DELETED(0,"未刪除"),
DELETED(1,"已刪除");
public?final?Integer?status;
public?final?String?name;
}
下圖是 java.lang.Enum 類:
而一個普通的類想實現序列化,只需要實現 Serializable 接口即可:
@Data
public?class?User?implements?Serializable?{
//序列化版本號
private?static?final?long?serialVersionUID?=?1111013L;
transient?private?String?name;
private?int?age;
public?static?void?main(String[]?args)?{
User?user?=?new?User();
user.setAge(12);
user.setName("小路飛");
System.out.println(user);
}
}
輸出結果如下:
User(name=小路飛,?age=12)
「那為什么一個類實現了 Serializable 接口,它就可以被序列化呢?」
這是因為它使用 ObjectOutputStream 來持久化對象到文件中,使用了 writeObject 方法,該方法又調用了如下方法:
/**
*?Underlying?writeObject/writeUnshared?implementation.
*/
private?void?writeObject0(Object?obj,?boolean?unshared)?throws?IOException?{
……
//?remaining?cases
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());
}
}
……
}
從上述代碼可知,如果被寫對象的類型是String,或數組,或 Enum,或 Serializable,那么就可以對該對象進行序列化,否則將拋出 NotSerializableException。
「即」:String 類型的對象、枚舉類型的對象、數組對象,都是默認可以被序列化的,并生成默認的序列化版本號。
我看網上的資料都有講使用 Externalizable 接口和使用 transient 關鍵字來實現序列化的方法,但阿粉感覺用的不多,所以在這里就不過多的贅述了,況且它們其實都是基于 Serializable 接口實現的序列化。
如何自動生成序列化版本號
idea IDE
安裝 serialVersionUID 插件即可。
eclipse
一般來說有兩種生成方式:
一個是默認的1L,比如: ? ? private static final long serialVersionUID = 1L;
一個是根據類名、接口名、成員方法及屬性等來生成一個64位的哈希字段。實現序列化后,類名上會出現黃色波浪下劃線,選擇第一項,添加已生成的串行版本標識即可自動生成一個。
我們都知道,新建一個對象的時候實現 Serializeable 接口,但為什么要這么做?什么時候這樣子做?這樣子做會不會出現幺蛾子?阿粉一個三連差點把自己都問懵逼了……
那接下來,大家就和阿粉一起簡單了解一下這個知識點吧……
序
序列化的定義是:將一個對象編碼成一個字節流(I/O);而與之相反的操作被稱為反序列化。
序列化的目的是為了方便數據的傳遞以及存儲到磁盤上(把一個Java對象寫入到硬盤或者傳輸到網路上面的其它計算機,這時我們就需要將對象轉換成字節流才能進行網絡傳輸。對于這種通用的操作,就出現了序列化來統一這些格式)。
簡單來說序列化就是一種用來處理對象流的機制。將對象轉化成字節序列后可以保存在磁盤上,或通過網絡傳輸,以達到以后恢復成原來的對象。序列化機制使得對象可以脫離程序的運行而獨立存在。
使用場景:所有可在網絡上傳輸的對象都必須是可序列化的,比如RMI(remote method invoke,即遠程方法調用),傳入的參數或返回的對象都是可序列化的,否則會出錯;所有需要保存到磁盤的java對象都必須是可序列化的。比如 Redis 將對象當做字符串存儲的時候,如果對象實現了序列化,則只需要將對象直接存儲即可(java會自動將對象轉換成序列化后的字節流);否則需要自己將對象轉換成 json 字符串存儲,不過 json 字符串相對更加節省內存空間一些。
通常建議:程序創建的每個JavaBean類都實現 Serializeable 接口。但是實現 Serializeable 接口也需要小心謹慎。正如《Effective Java》中第 74 條提到的那樣:
如何實現序列化
在 Java 中,只要一個類實現了 java.io.Serializable 接口,它就可以被序列化(枚舉類也可以被序列化)。
例如:每個枚舉類型都會默認繼承類java.lang.Enum,而Enum類實現了Serializable接口,所以枚舉類型對象都是默認可以被序列化的。
//?DeletedEnum?類,表示刪除狀態
@Getter
@AllArgsConstructor
public?enum?DeletedEnum?{
NO_DELETED(0,"未刪除"),
DELETED(1,"已刪除");
public?final?Integer?status;
public?final?String?name;
}
下圖是 java.lang.Enum 類:
而一個普通的類想實現序列化,只需要實現 Serializable 接口即可:
@Data
public?class?User?implements?Serializable?{
//序列化版本號
private?static?final?long?serialVersionUID?=?1111013L;
transient?private?String?name;
private?int?age;
public?static?void?main(String[]?args)?{
User?user?=?new?User();
user.setAge(12);
user.setName("小路飛");
System.out.println(user);
}
}
輸出結果如下:
User(name=小路飛,?age=12)
「那為什么一個類實現了 Serializable 接口,它就可以被序列化呢?」
這是因為它使用 ObjectOutputStream 來持久化對象到文件中,使用了 writeObject 方法,該方法又調用了如下方法:
/**
*?Underlying?writeObject/writeUnshared?implementation.
*/
private?void?writeObject0(Object?obj,?boolean?unshared)?throws?IOException?{
……
//?remaining?cases
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());
}
}
……
}
從上述代碼可知,如果被寫對象的類型是String,或數組,或 Enum,或 Serializable,那么就可以對該對象進行序列化,否則將拋出 NotSerializableException。
「即」:String 類型的對象、枚舉類型的對象、數組對象,都是默認可以被序列化的,并生成默認的序列化版本號。
我看網上的資料都有講使用 Externalizable 接口和使用 transient 關鍵字來實現序列化的方法,但阿粉感覺用的不多,所以在這里就不過多的贅述了,況且它們其實都是基于 Serializable 接口實現的序列化。
如何自動生成序列化版本號
idea IDE
安裝 serialVersionUID 插件即可。
eclipse
一般來說有兩種生成方式:
一個是默認的1L,比如: ? ? private static final long serialVersionUID = 1L;
一個是根據類名、接口名、成員方法及屬性等來生成一個64位的哈希字段。實現序列化后,類名上會出現黃色波浪下劃線,選擇第一項,添加已生成的串行版本標識即可自動生成一個。
序列化版本號的用處
在 java.io.Serializable 的文檔中的解釋是這樣的:
「大致意思是如下」:
因為反序列化必須擁有 class 文件,但隨著項目的升級,class 文件也會升級,序列化怎么保證升級前后的兼容性呢?
序列化運行時與每個可序列化的類關聯一個版本號,稱為 serialVersionUID,在反序列化期間使用該版本號來驗證序列化對象的發送者和接收者是否已加載了該對象的與序列化兼容的類。如果接收方已為該對象加載了一個與相應發送方類具有不同的 serialVersionUID 的類,則反序列化將導致 InvalidClassException。可序列化的類可以通過聲明一個名為 serialVersionUID 的字段來顯式聲明其自己的 serialVersionUID,該字段必須是靜態的,最終的且類型為 long:
ANY-ACCESS-MODIFIER?static?final?long?serialVersionUID?=?42L;
只要版本號相同,即使更改了序列化屬性,對象也可以正確被反序列化回來。
@Data
public?class?User?implements?Serializable?{
//序列化版本號
private?static?final?long?serialVersionUID?=?1111013L;
private?String?name;
private?int?age;
}
如果反序列化使用的 class 的版本號與序列化時使用的不一致,反序列化會報 InvalidClassException 異常。
如果可序列化的類未明確聲明 serialVersionUID ,則序列化運行時將根據該類的各個方面為該類計算默認的 serialVersionUID 值,如Java(TM)對象序列化規范中所述。但是,強烈建議所有可序列化的類顯式聲明 serialVersionUID 值,因為默認的 serialVersionUID 計算對類詳細信息高度敏感,而類詳細信息可能會根據編譯器的實現而有所不同,因此可能在反序列化期間導致意外的 InvalidClassExceptions。
而且,默認值不利于 jvm 間的移植,可能class文件沒有更改,但不同 jvm 可能計算的規則不一樣,這樣也會導致無法反序列化。
因此,為了保證不同Java編譯器實現之間的 serialVersionUID 值一致,可序列化的類必須聲明一個顯式的 serialVersionUID 值。強烈建議顯式 serialVersionUID 聲明在可能的情況下使用 private 修飾符,因為此類聲明僅適用于立即聲明的類 serialVersionUID字段作為繼承成員不起作用。
最后,使用默認機制在序列化對象時,不僅會序列化當前對象,還會對該對象引用的其它對象也進行序列化,同樣地,這些其它對象引用的另外對象也將被序列化,以此類推。所以,如果一個對象包含的成員變量是容器類對象,而這些容器所含有的元素也是容器類對象,那么這個序列化的過程就會較復雜,開銷也較大。
參考
https://www.cnblogs.com/kubixuesheng/p/10350523.html
https://stackoverflow.com/questions/285793/what-is-a-serialversionuid-and-why-should-i-use-it
https://juejin.im/post/5ce3cdc8e51d45777b1a3cdf
《Effective Java》中文版第二版
Java 彈性文件服務
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。