本篇內(nèi)容介紹了“java對象的序列化和反序列化是什么意思”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供桐廬網(wǎng)站建設(shè)、桐廬做網(wǎng)站、桐廬網(wǎng)站設(shè)計、桐廬網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計與制作、桐廬企業(yè)網(wǎng)站模板建站服務(wù),十年桐廬做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡(luò)服務(wù)。
序列化的原本意圖是希望對一個Java對象作一下“變換”,變成字節(jié)序列,這樣一來方便持久化存儲到磁盤,避免程序運(yùn)行結(jié)束后對象就從內(nèi)存里消失,另外變換成字節(jié)序列也更便于網(wǎng)絡(luò)運(yùn)輸和傳播,所以概念上很好理解:
序列化:把Java對象轉(zhuǎn)換為字節(jié)序列。
反序列化:把字節(jié)序列恢復(fù)為原先的Java對象。
而且序列化機(jī)制從某種意義上來說也彌補(bǔ)了平臺化的一些差異,畢竟轉(zhuǎn)換后的字節(jié)流可以在其他平臺上進(jìn)行反序列化來恢復(fù)對象。
在Java中,如果一個對象要想實現(xiàn)序列化,必須要實現(xiàn)下面兩個接口之一:
Serializable 接口
Externalizable 接口
那這兩個接口是如何工作的呢?兩者又有什么關(guān)系呢?我們分別進(jìn)行介紹。
一個對象想要被序列化,那么它的類就要實現(xiàn)此接口或者它的子接口。
這個對象的所有屬性(包括private屬性、包括其引用的對象)都可以被序列化和反序列化來保存、傳遞。不想序列化的字段可以使用transient修飾。
由于Serializable對象完全以它存儲的二進(jìn)制位為基礎(chǔ)來構(gòu)造,因此并不會調(diào)用任何構(gòu)造函數(shù),因此Serializable類無需默認(rèn)構(gòu)造函數(shù),但是當(dāng)Serializable類的父類沒有實現(xiàn)Serializable接口時,反序列化過程會調(diào)用父類的默認(rèn)構(gòu)造函數(shù),因此該父類必需有默認(rèn)構(gòu)造函數(shù),否則會拋異常。
使用transient關(guān)鍵字阻止序列化雖然簡單方便,但被它修飾的屬性被完全隔離在序列化機(jī)制之外,導(dǎo)致了在反序列化時無法獲取該屬性的值,而通過在需要序列化的對象的Java類里加入writeObject()方法與readObject()方法可以控制如何序列化各屬性,甚至完全不序列化某些屬性或者加密序列化某些屬性。
它是Serializable接口的子類,用戶要實現(xiàn)的writeExternal()和readExternal() 方法,用來決定如何序列化和反序列化。
因為序列化和反序列化方法需要自己實現(xiàn),因此可以指定序列化哪些屬性,而transient在這里無效。
對Externalizable對象反序列化時,會先調(diào)用類的無參構(gòu)造方法,這是有別于默認(rèn)反序列方式的。如果把類的不帶參數(shù)的構(gòu)造方法刪除,或者把該構(gòu)造方法的訪問權(quán)限設(shè)置為private、默認(rèn)或protected級別,會拋出java.io.InvalidException: no valid constructor異常,因此Externalizable對象必須有默認(rèn)構(gòu)造函數(shù),而且必需是public的。
使用時,你只想隱藏一個屬性,比如用戶對象user的密碼pwd,如果使用Externalizable,并除了pwd之外的每個屬性都寫在writeExternal()方法里,這樣顯得麻煩,可以使用Serializable接口,并在要隱藏的屬性pwd前面加上transient就可以實現(xiàn)了。如果要定義很多的特殊處理,就可以使用Externalizable。
當(dāng)然這里我們有一些疑惑,Serializable 中的writeObject()方法與readObject()方法科可以實現(xiàn)自定義序列化,而Externalizable 中的writeExternal()和readExternal() 方法也可以,他們有什么異同呢?
readExternal(),writeExternal()兩個方法,這兩個方法除了方法簽名和readObject(),writeObject()兩個方法的方法簽名不同之外,其方法體完全一樣。
需要指出的是,當(dāng)使用Externalizable機(jī)制反序列化該對象時,程序會使用public的無參構(gòu)造器創(chuàng)建實例,然后才執(zhí)行readExternal()方法進(jìn)行反序列化,因此實現(xiàn)Externalizable的序列化類必須提供public的無參構(gòu)造。
雖然實現(xiàn)Externalizable接口能帶來一定的性能提升,但由于實現(xiàn)ExternaLizable接口導(dǎo)致了編程復(fù)雜度的增加,所以大部分時候都是采用實現(xiàn)Serializable接口方式來實現(xiàn)序列化。
然而Java目前并沒有一個關(guān)鍵字可以直接去定義一個所謂的“可持久化”對象。
對象的持久化和反持久化需要靠程序員在代碼里手動顯式地進(jìn)行序列化和反序列化還原的動作。
舉個例子,假如我們要對Student類對象序列化到一個名為student.txt的文本文件中,然后再通過文本文件反序列化成Student類對象:
1、Student類定義
public class Student implements Serializable { private String name; private Integer age; private Integer score; @Override public String toString() { return "Student:" + '\n' + "name = " + this.name + '\n' + "age = " + this.age + '\n' + "score = " + this.score + '\n' ; } // ... 其他省略 ... }
2、序列化
public static void serialize( ) throws IOException { Student student = new Student(); student.setName("CodeSheep"); student.setAge( 18 ); student.setScore( 1000 ); ObjectOutputStream objectOutputStream = new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) ); objectOutputStream.writeObject( student ); objectOutputStream.close(); System.out.println("序列化成功!已經(jīng)生成student.txt文件"); System.out.println("=============================================="); }
3、反序列化
public static void deserialize( ) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream( new FileInputStream( new File("student.txt") ) ); Student student = (Student) objectInputStream.readObject(); objectInputStream.close(); System.out.println("反序列化結(jié)果為:"); System.out.println( student ); }
4、運(yùn)行結(jié)果
控制臺打?。?/p>
序列化成功!已經(jīng)生成student.txt文件 ============================================== 反序列化結(jié)果為: Student: name = CodeSheep age = 18 score = 1000
上面在定義Student類時,實現(xiàn)了一個Serializable接口,然而當(dāng)我們點進(jìn)Serializable接口內(nèi)部查看,發(fā)現(xiàn)它竟然是一個空接口,并沒有包含任何方法!
試想,如果上面在定義Student類時忘了加implements Serializable時會發(fā)生什么呢?
實驗結(jié)果是:此時的程序運(yùn)行會報錯,并拋出NotSerializableException異常:
我們按照錯誤提示,由源碼一直跟到ObjectOutputStream的writeObject0()方法底層一看,才恍然大悟:
如果一個對象既不是字符串、數(shù)組、枚舉,而且也沒有實現(xiàn)Serializable接口的話,在序列化時就會拋出NotSerializableException異常!
原來Serializable接口也僅僅只是做一個標(biāo)記用?。?!它告訴代碼只要是實現(xiàn)了Serializable接口的類都是可以被序列化的!然而真正的序列化動作不需要靠它完成。
相信你一定經(jīng)??吹接行╊愔卸x了如下代碼行,即定義了一個名為serialVersionUID的字段:
private static final long serialVersionUID = -4392658638228508589L;
你知道這句聲明的含義嗎?為什么要搞一個名為serialVersionUID的序列號?
繼續(xù)來做一個簡單實驗,還拿上面的Student類為例,我們并沒有人為在里面顯式地聲明一個serialVersionUID字段。
我們首先還是調(diào)用上面的serialize()方法,將一個Student對象序列化到本地磁盤上的student.txt文件:
接下來我們在Student類里面動點手腳,比如在里面再增加一個名為id的字段,表示學(xué)生學(xué)號:
public class Student implements Serializable { private String name; private Integer age; private Integer score; private Integer id;
這時候,我們拿剛才已經(jīng)序列化到本地的student.txt文件,還用如下代碼進(jìn)行反序列化,試圖還原出剛才那個Student對象:
運(yùn)行發(fā)現(xiàn)報錯了,并且拋出了InvalidClassException異常
這地方提示的信息非常明確了:序列化前后的serialVersionUID號碼不兼容!
從這地方最起碼可以得出兩個重要信息:
1、serialVersionUID是序列化前后的唯一標(biāo)識符
2、默認(rèn)如果沒有人為顯式定義過serialVersionUID,那編譯器會為它自動聲明一個!
第1個問題: serialVersionUID序列化ID,可以看成是序列化和反序列化過程中的“暗號”,在反序列化時,JVM會把字節(jié)流中的序列號ID和被序列化類中的序列號ID做比對,只有兩者一致,才能重新反序列化,否則就會報異常來終止反序列化的過程。
第2個問題: 如果在定義一個可序列化的類時,沒有人為顯式地給它定義一個serialVersionUID的話,則Java運(yùn)行時環(huán)境會根據(jù)該類的各方面信息自動地為它生成一個默認(rèn)的serialVersionUID,一旦像上面一樣更改了類的結(jié)構(gòu)或者信息,則類的serialVersionUID也會跟著變化!
所以,為了serialVersionUID的確定性,寫代碼時還是建議,凡是implements Serializable的類,都最好人為顯式地為它聲明一個serialVersionUID明確值!
當(dāng)然,如果不想手動賦值,你也可以借助IDE的自動添加功能,比如我使用的IntelliJ IDEA,按alt + enter就可以為類自動生成和添加serialVersionUID字段,十分方便:
1、凡是被static修飾的字段是不會被序列化的
2、凡是被transient修飾符修飾的字段也是不會被序列化的
對于第一點,因為序列化保存的是對象的狀態(tài)而非類的狀態(tài),所以會忽略static靜態(tài)域也是理所應(yīng)當(dāng)?shù)摹?/p>
對于第二點,就需要了解一下transient修飾符的作用了。
如果在序列化某個類的對象時,就是不希望某個字段被序列化(比如這個字段存放的是隱私值,如:密碼等),那這時就可以用transient修飾符來修飾該字段。
比如在之前定義的Student類中,加入一個密碼字段,但是不希望序列化到txt文本,則可以:
public class Student implements Serializable { private static final long serialVersionUID = -4392658638228508589L; private transient String name; private Integer age; private Integer score; private transient String passwd;
這樣在序列化Student類對象時,password字段會設(shè)置為默認(rèn)值null,這一點可以從反序列化所得到的結(jié)果來看出:
public static void serialize() throws IOException { Student student = new Student(); student.setName("CodeSheep"); student.setAge(18); student.setScore(1000); student.setPasswd("123");
public UserInfo() { userAge=20;//這個是在第二次測試使用,判斷反序列化是否通過構(gòu)造器 } public void writeExternal(ObjectOutput out) throws IOException { // 指定序列化時候?qū)懭氲膶傩?。這里仍然不寫入年齡 out.writeObject(userName); out.writeObject(usePass); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // 指定反序列化的時候讀取屬性的順序以及讀取的屬性 // 如果你寫反了屬性讀取的順序,你可以發(fā)現(xiàn)反序列化的讀取的對象的指定的屬性值也會與你寫的讀取方式一一對應(yīng)。因為在文件中裝載對象是有序的 userName=(String) in.readObject(); usePass=(String) in.readObject(); }
我們在序列化對象的時候,由于這個類實現(xiàn)了Externalizable 接口,在writeExternal()方法里定義了哪些屬性可以序列化,哪些不可以序列化,所以,對象在經(jīng)過這里就把規(guī)定能被序列化的序列化保存文件,不能序列化的不處理,然后在反序列的時候自動調(diào)用readExternal()方法,根據(jù)序列順序挨個讀取進(jìn)行反序列,并自動封裝成對象返回,然后在測試類接收,就完成了反序列。
Externalizable 實例類的唯一特性是可以被寫入序列化流中,該類負(fù)責(zé)保存和恢復(fù)實例內(nèi)容。 若某個要完全控制某一對象及其超類型的流格式和內(nèi)容,則它要實現(xiàn) Externalizable 接口的 writeExternal 和 readExternal 方法。這些方法必須顯式與超類型進(jìn)行協(xié)調(diào)以保存其狀態(tài)。這些方法將代替定制的 writeObject 和 readObject 方法實現(xiàn)。
writeExternal(ObjectOutput out)
該對象可實現(xiàn) writeExternal 方法來保存其內(nèi)容,它可以通過調(diào)用 DataOutput 的方法來保存其基本值,或調(diào)用 ObjectOutput 的 writeObject 方法來保存對象、字符串和數(shù)組。
readExternal(ObjectInput in)
對象實現(xiàn) readExternal 方法來恢復(fù)其內(nèi)容,它通過調(diào)用 DataInput 的方法來恢復(fù)其基礎(chǔ)類型,調(diào)用 readObject 來恢復(fù)對象、字符串和數(shù)組。
1、實現(xiàn)serializable接口是默認(rèn)序列化所有屬性,如果有不需要序列化的屬性使用transient修飾。externalizable接口是serializable的子類,實現(xiàn)這個接口需要重寫writeExternal和readExternal方法,指定對象序列化的屬性和從序列化文件中讀取對象屬性的行為。
2、實現(xiàn)serializable接口的對象序列化文件進(jìn)行反序列化不走構(gòu)造方法,載入的是該類對象的一個持久化狀態(tài),再將這個狀態(tài)賦值給該類的另一個變量。實現(xiàn)externalizable接口的對象序列化文件進(jìn)行反序列化先走構(gòu)造方法得到控對象,然后調(diào)用readExternal方法讀取序列化文件中的內(nèi)容給對應(yīng)的屬性賦值。
從上面的過程可以看出,序列化和反序列化的過程其實是有漏洞的,因為從序列化到反序列化是有中間過程的,如果被別人拿到了中間字節(jié)流,然后加以偽造或者篡改,那反序列化出來的對象就會有一定風(fēng)險了。
畢竟反序列化也相當(dāng)于一種 “隱式的”對象構(gòu)造 ,因此我們希望在反序列化時,進(jìn)行受控的對象反序列化動作。
那怎么個受控法呢?
答案就是: 自行編寫readObject()函數(shù),用于對象的反序列化構(gòu)造,從而提供約束性。
既然自行編寫readObject()函數(shù),那就可以做很多可控的事情:比如各種判斷工作。
還以上面的Student類為例,一般來說學(xué)生的成績應(yīng)該在0 ~ 100之間,我們?yōu)榱朔乐箤W(xué)生的考試成績在反序列化時被別人篡改成一個奇葩值,我們可以自行編寫readObject()函數(shù)用于反序列化的控制:
private void readObject( ObjectInputStream objectInputStream ) throws IOException, ClassNotFoundException { // 調(diào)用默認(rèn)的反序列化函數(shù) objectInputStream.defaultReadObject(); // 手工檢查反序列化后學(xué)生成績的有效性,若發(fā)現(xiàn)有問題,即終止操作! if( 0 > score || 100 < score ) { throw new IllegalArgumentException("學(xué)生分?jǐn)?shù)只能在0到100之間!"); } }
比如我故意將學(xué)生的分?jǐn)?shù)改為101,此時反序列化立馬終止并且報錯:
對于上面的代碼,為什么自定義的private的readObject()方法可以被自動調(diào)用,跟一下底層源碼來一探究竟,跟到了ObjectStreamClass類的最底層,是反射機(jī)制在起作用!是的,在Java里,果然萬物皆可“反射”(滑稽),即使是類中定義的private私有方法,也能被摳出來執(zhí)行了,簡直引起舒適了。
一個容易被忽略的問題是:可序列化的單例類有可能并不單例!
舉個代碼小例子就清楚了。
比如這里我們先用java寫一個常見的「靜態(tài)內(nèi)部類」方式的單例模式實現(xiàn):
public class Singleton implements Serializable { private static final long serialVersionUID = -1576643344804979563L; private Singleton() { } private static class SingletonHolder { private static final Singleton singleton = new Singleton(); } public static synchronized Singleton getSingleton() { return SingletonHolder.singleton; } }
然后寫一個驗證主函數(shù):
public class Test2 { public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectOutputStream objectOutputStream = new ObjectOutputStream( new FileOutputStream( new File("singleton.txt") ) ); // 將單例對象先序列化到文本文件singleton.txt中 objectOutputStream.writeObject( Singleton.getSingleton() ); objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream( new FileInputStream( new File("singleton.txt") ) ); // 將文本文件singleton.txt中的對象反序列化為singleton1 Singleton singleton1 = (Singleton) objectInputStream.readObject(); objectInputStream.close(); Singleton singleton2 = Singleton.getSingleton(); // 運(yùn)行結(jié)果竟打印 false ! System.out.println( singleton1 == singleton2 ); } }
運(yùn)行后我們發(fā)現(xiàn):反序列化后的單例對象和原單例對象并不相等了,這無疑沒有達(dá)到我們的目標(biāo)。
解決辦法是:在單例類中手寫readResolve()函數(shù),直接返回單例對象:
private Object readResolve() { return SingletonHolder.singleton; } package serialize.test; import java.io.Serializable; public class Singleton implements Serializable { private static final long serialVersionUID = -1576643344804979563L; private Singleton() { } private static class SingletonHolder { private static final Singleton singleton = new Singleton(); } public static synchronized Singleton getSingleton() { return SingletonHolder.singleton; } private Object readResolve() { return SingletonHolder.singleton; } }
這樣一來,當(dāng)反序列化從流中讀取對象時,readResolve()會被調(diào)用,用其中返回的對象替代反序列化新建的對象。
“java對象的序列化和反序列化是什么意思”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
文章名稱:java對象的序列化和反序列化是什么意思
文章路徑:http://aaarwkj.com/article44/peiche.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供做網(wǎng)站、標(biāo)簽優(yōu)化、App開發(fā)、微信小程序、網(wǎng)站建設(shè)、網(wǎng)站改版
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)