Java中的序列化(Serializable)和反序列化
JAVA序列化與反序列化
JAVA序列化與反序列化就是JAVA對象與一串字節(jié)流之間的相互轉換, 我們在程序中創(chuàng)建的JAVA對象只存在于JVM中, 當程序退出時, 這些對象也就消失了, 而序列化正是為了將這些對象保存起來以僅將來使用,也可以將已經(jīng)序列化的對象傳送給其他JVM來使用,這些序列化的字節(jié)流是于JVM無關的, 也就是說一個JVM序列化的對象可以在另一個JVM中反序列化
使用JAVA提供的序列化機制有以下兩條需要遵守的條件:
- 該類必須直接實現(xiàn)java.io.Serializable接口或者間接從其繼承樹中實現(xiàn)該接口(也就是他的某個父類實現(xiàn)了這個接口);
- 對于該類的所有無法序列化的屬性(本文指字段field, 而不是嚴格意義上的屬性property, 下同)必須使用transient修飾.
對以上兩個條件的補充說明:
- 從其繼承樹中實現(xiàn)Serializable接口指的是該類的某個父類實現(xiàn)了這個接口, 要注意的是Object類并沒有實現(xiàn)該接口, 也就是說默認的情況下我們定義的類是不支持序列化的, 而JDK提供的某些類如String, 數(shù)組等實現(xiàn)了該接口;
- 無法序列化的屬性包括兩種:一種是主觀上不想保存的屬性, 如動態(tài)生成的屬性或者考慮到性能上的要求不準備保存的屬性; 另一種是由于該屬性的類型沒有實現(xiàn)序列化而無法保存的屬性, 如Thread類型的屬性;
- 序列化機制并不要求該類具有一個無參的構造方法, 因為在反序列化的過程中實際上是去其繼承樹上找到一個沒有實現(xiàn)Serializable接口的父類(最終會找到Object), 然后構造該類的對象, 再逐層往下的去設置各個可以反序列化的屬性(也就是沒有被transient修飾的非靜態(tài)屬性).
在使用JDK提供的序列化機制時需要借助一對I/O流, ObjectOutputStream和ObjectInputStream, 這兩個流分別是進行序列化和反序列化操作, 通過ObjectOutputStream類的writeObject(obj)方法可以將對象寫入到輸出流中, 通過ObjectInputStream類的readObject()方法可以從該輸入流中反序列化該對象出來. 如下面的例子就是將Student類的一個對象序列化保存到文件中, 并從該文件反序列化重新構建出該對象(僅僅為了展示如何使用,對異常及流的處理末加以注意):
class Student implements Serializable{ private String name; public Student(String name){ this.name = name; } public String getName(){ return name; } } class StudentSerializer{ public static void main(String[] args) throws Exception{ // create a Student object Student st = new Student("jason"); // serialize the st to jason.se file ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("jason.se")); oos.writeObject(st); oos.close(); // deserialize the object from jason.se ObjectInputStream ois = new ObjectInputStream(new FileInputStream("jason.se")); Student jason = (Student) ois.readObject(); ois.close(); // verify the name field of jason object assert "jason".equals(jason.getName()); } }
從上例可以看出, JDK提供的序列化機制使用起來相當簡單, 要注意的是:
- 在反序列的JVM上必須能夠找到該類(有可能序列化和反序列化并不是在同一個JVM上進行的), 否則就會拋出ClassNotFoundException;
- 由于ObjectInputStream.readObject()方法可以反序列化任何類的對象, 所以其返回類型為Object, 我們需要將其強轉成具體的類;
- 如何對不滿足序列化機制的兩個要求的類進行序列化, 則會拋出NotSerializableException;
- 如果JVM發(fā)現(xiàn)序列化與反序列化的類文件"不相同", 則會拋出InvalidClassException.
JVM如何判斷序列化與反序列化的類文件是否相同呢? 并不是說兩個類文件要完全一樣, 而是通過類的一個私有屬性serialVersionUID來判斷的, 如果我們沒有顯示的指定這個屬性, 那么JVM會自動使用該類的hashcode值來設置這個屬性, 這個時候如果我們對類進行改變(比如說加一個屬性或者刪掉一個屬性)就會導致serialVersionUID不同, 所以對于準備序列化的類, 一般情況下我們都會顯示的設置這個屬性, 這樣及時以后我們對該類進行了某些改動, 只要這個值保持一樣, JVM就還是會認為這個類文件是沒有變的, 需要注意的是這種改變不包括繼承結構的變化. 該屬性必須以下面的修飾方法來設置:
//當然這個值可以自己指定, 也可以通過JDK提供的serializer來查看其默認的hashcode值. private static final long serialVersionUID = -4333316296251054416L;
由于JDK提供的這種默認的序列化機制是簡單的將對象變成字節(jié)流, 有時候并不滿足我們的要求, 比如考慮到加密, 或者在反序列化完了后需要調(diào)用某個方法來初始化transient的屬性等等, JDK提供了一種擴展的方法來增加對序列化和反序列化的控制. 那就是可以讓序列化的對象實現(xiàn)下面兩個固定的方法(注意修飾符和結構是固定的, throws的exception可以變通, 比如直接寫成throws Exception也是可以的):
private void writeObject(ObjectOutputStream oos) throws IOException {} private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException{}
JVM在序列化和反序列化的過程中如果發(fā)現(xiàn)我們的類實現(xiàn)了這兩個方法, 就會在writeObject(obj)和readObject()的時候將控制流轉交給這兩個方法, 這樣我們就可以來執(zhí)行一些額外的操作, 同時可以在這兩個方法中調(diào)用ObjectOutputStream的defaultWriteObject()和ObjectInputStream的defaultReadObject()來讓JVM幫我們來執(zhí)行底部的具體序列化和反序列化操作, 如下例所示:
class Student implements Serializable{ private String name; public Student(String name){ this.name = name; } public String getName(){ return name; } private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); // 可以執(zhí)行其他的操作, 比如對反列化的文件進行加密等等 } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); //可以調(diào)用其他方法來進行額外的初始化操作 } }
注意此時僅僅是對需要序列化的類增加了這兩個私有方法, 而對如何將其序列化和反序列化上并沒有任何改變, 也就是說我們的StudentSerializer類并不需要做任何改變, JVM的序列化機制會自行來控制, 當然如果我們在兩個私有的writeObject()和readObject()中并沒有調(diào)回ObjectOutputStream的defaultWriteObject()和ObjectInputStream的defaultReadObject()方法的話, 那就并沒有序列化了(除非你自己實現(xiàn)這個序列化和反序列化的過程)
到此這篇關于Java中的序列化(Serializable)和反序列化的文章就介紹到這了,更多相關Java序列化與反序列化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
微信小程序獲取手機號的完整實例(Java后臺實現(xiàn))
我們在做小程序開發(fā)的過程中,經(jīng)常會涉及到用戶身份的問題,最普遍的就是我們要獲取用戶的手機號碼,下面這篇文章主要給大家介紹了關于微信小程序獲取手機號的完整實例,后臺由Java實現(xiàn),需要的朋友可以參考下2022-06-06mybatis中<if>標簽bool值類型為false判斷方法
這篇文章主要給大家介紹了關于mybatis中<if>標簽bool值類型為false判斷方法,文中通過示例代碼介紹的非常詳細,對大家學習或者使用mybatis具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-08-08springboot實現(xiàn)https雙向傳輸協(xié)議的示例代碼
本文主要介紹了springboot實現(xiàn)https雙向傳輸協(xié)議的示例代碼,包含配置證書和私鑰路徑、調(diào)用請求方法等步驟,具有一定的參考價值,感興趣的可以了解一下2025-03-03JavaWeb 文件的上傳和下載功能簡單實現(xiàn)代碼
這篇文章主要介紹了JavaWeb 文件的上傳和下載功能簡單實現(xiàn)代碼,需要的朋友可以參考下2017-04-04