JAVA序列化結(jié)論驗證

static字段不會被序列化

//這是父類
class Data implements Serializable{
    private int n;
    public Data(int n){
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}
//這是子類煌往,繼承父類自動繼承Serializable接口
class sub extends Data{
    private static int m = 6;
    private int k = 10;
    public sub(int n) {
        super(n);
    }
}

父類Data有一個n的屬性,通過構(gòu)造器賦值
子類sub有額外的靜態(tài)屬性m,非靜態(tài)屬性k=10

父類屬性 子類屬性
父類Data n
子類sub 繼承來的n 靜態(tài)m=6,非靜態(tài)k=10

假設(shè)我們構(gòu)造一個子類對象 new sub(5)
那么當(dāng)前子類對象的屬性為

父類屬性 子類屬性
父類Data n = 5
子類sub 繼承來的n=5 靜態(tài)m=6,非靜態(tài)k=10

按照序列化的結(jié)論刽脖,靜態(tài)屬性不會被序列化羞海,那么猜測序列化再反序列化之后屬性應(yīng)該為

父類屬性 子類屬性
父類Data n
子類sub 繼承來的n(未初始化,默認(rèn)為0) 靜態(tài)m=6,非靜態(tài)k=10

這里我省略了序列化和反序列化的過程曲管,直接上代碼結(jié)果


序列化結(jié)果

這里只有k 和 n 因為 n沒有初始化 所以不顯示却邓,所以第一個結(jié)論驗證成功,這是當(dāng)然的,因為序列化只是為了序列化對象的狀態(tài)
tip:如果在一個JVM存活周期內(nèi)院水,在本地JVM進行序列化和反序列化腊徙,是有可能讀到n=10時的,這個n=10不是序列化得到的檬某,而是JVM在方法區(qū)找到new出來對象時的n

反序列化什么時候需要一個空參構(gòu)造器

這部分篇幅較長撬腾,涉及到源碼部分,有興趣的可以查看我的另一篇文章
從源碼解析JAVA序列化是否需要空參構(gòu)造方法

序列化會遞歸域?qū)ο蟮男蛄谢?/h3>

這個驗證看起來可能會有點吃力恢恼,我用的《thinking in java》上的例子

class Data implements Serializable{
    private int n;
    public Data(int n){
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}
public class Worm implements Serializable{
    private static Random rand = new Random(47);
    private Data[] d = {
            new Data(rand.nextInt(10)),
            new Data(rand.nextInt(10)),
            new Data(rand.nextInt(10))
    };
    private Worm next;
    private char c;

    public Worm(int i,char x){
        System.out.println("Worm constructor: " + i);
        c = x;
        if(-- i > 0){
            next = new Worm(i , (char)(x+1));
        }
    }
    public Worm(){
        System.out.println("default constructor");
    }

    public String toString(){
        StringBuilder result = new StringBuilder(":");
        result.append(c);
        result.append("(");
        for(Data dat : d)
            result.append(dat);
        result.append(")");
        if(next != null)
            result.append(next);
        return result.toString();
    }

  //序列化和反序列化調(diào)用的方法入口
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Worm w =new Worm(6,'a');
        System.out.println("w = " + w);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"));
        out.writeObject("Worm storage\n");
        out.writeObject(w);
        out.close();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"));
        String s = (String)in.readObject();
        Worm w2 = (Worm) in.readObject();
        System.out.println(s + "w2 = " + w2);
    }
}

簡單描述一下民傻,Worm(蠕蟲)類似一個鏈表的結(jié)構(gòu),有一個next指針指向下一個Worm,而每一個Worm有三個Date是隨機生成的场斑,toString方法會將Worm及Worm鏈接的next都打印出來漓踢,這里我們指定new Worm(6);
使用調(diào)試工具



序列化前的worm,有6個節(jié)點,每個節(jié)點的data都是隨機生成的漏隐,而序列化會保存域的對象喧半,已經(jīng)遞歸的序列化的對象,那么我們猜測反序列化回來的w應(yīng)該也是同樣的結(jié)構(gòu),直接上結(jié)果



w2是反序列化回來的結(jié)果锁保,可以看出不僅對象的域相同薯酝,對象的next對象都同樣序列化了

不指定序列號,編譯器會自動生成一個UID

還是用老朋友爽柒,data類

class Data implements Serializable{
    private int n;
    public Data(int n){
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}

可以看到?jīng)]有指定序列號吴菠,沒有指定序列號的時候,編譯器會自動根據(jù)Class哈希出一個UID浩村,這樣只要對類稍微改動則版本就會發(fā)生改變

        Data w = new Data(10);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"));
        out.writeObject(w);
        out.close();

這里用舊Data類持久化一個對象做葵,然后我們對Data類稍微小改造,增加一個字段k

class Data implements Serializable{
    private int k;//新增加的字段
    private int n;
    public Data(int n){
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}

按照推測心墅,這個Data的UID已經(jīng)發(fā)生變化酿矢,反序列化時因為UID與持久化對象的UID不同,會丟出InvalidClassException異常而反序列失敗怎燥,我們上結(jié)果

Exception in thread "main" java.io.InvalidClassException: main.Data; local class incompatible: stream classdesc serialVersionUID = -1762858249998764225, local class serialVersionUID = -2944979104297389356
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
    at main.Worm.main(Worm.java:76)

驗證成功了瘫筐,說明確實編譯器會為沒有UID的對象根據(jù)類文件自動哈希一個UID,以便接受方驗證類版本是否發(fā)生變化铐姚,但是不推薦不注明UID策肝,因為自動編譯的UID不可控肛捍,可能兩個類文件結(jié)構(gòu)相同,但是格式具有區(qū)別节槐,或者是平臺的區(qū)別装处,導(dǎo)致UID不同序列化失敗遗遵,因此我們應(yīng)該自己維護一個UID叉趣,并且每次對類修改時對UID進行更新。

指定類版本UID桦山,但發(fā)送方和接收方類UID不相同時尘分,可以序列化

序列化和反序列化時只會根據(jù)UID來驗證類的一致性排拷,因此有趣的是膘婶,即使序列化和反序列化的類文件結(jié)構(gòu)不同缺前,但是只要UID一樣,也能夠序列化成功悬襟,但是會丟失數(shù)據(jù)诡延。
我們分情況驗證:

  1. 序列化的類比反序列化的類多一條字段
    還是Data,序列化的Data多出來一個k字段
class Data implements Serializable{
private static final long serialVersionUID = 1L;//這里指定了UID
    private int k = 10;//比反序列化的Data多的字段
    private int n;
    public Data(int n){
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}

反序列化的Data沒有k

class Data implements Serializable{
    private static final long serialVersionUID = 1L;//這里指定了UID
    //private int k = 10;
    private int n;
    public Data(int n){
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}

然后我們將Data序列化


有兩個字段k=10,n=10;
那么反序列能成功嗎,反序列之后k還存在嗎


這里反序列化成功古胆,并且反序列化之后k被丟棄了,這里我們可以得出結(jié)論:
序列化UID相同時筛璧,即使類版本不同逸绎,也能夠序列化成功,序列化前多出來的字段會被丟棄

  1. 反序列化的類比序列化的類多一條字段
    這里不貼詳細(xì)代碼了夭谤,只要把上面兩個類相反就可以了
    最后得出的結(jié)論是:
    序列化UID相同時棺牧,即使類版本不同,也能夠序列化成功朗儒,反序列化多出來的字段不會得到賦值

發(fā)送方序列化子類類型颊乘,接受反反序列化用父類也可以接受

舉個例子

class Data implements Serializable{
    private int n;
    public Data(int n){
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}

class sub extends Data{
    private static int m = 6;
    private int k;
    public sub(int n) {
        super(n);
    }
}

sub繼承Data,序列化時我將子類sub的一個對象序列化,反序列化時我用父類Data接受,反序列化的結(jié)果會自動向上轉(zhuǎn)型嗎
首先序列化一個子類sub對象k=0,n=10



然后反序列化我會用Data去接收

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"));
        Data w2 = (Data) in.readObject();//接受序列化的子類Sub對象

序列化成功醉锄,同時通過debug發(fā)現(xiàn)乏悄,即時用Data接收,w2對象仍然是一個sub類型



為什么會這樣呢恳不,我們打開序列化的文件

?í ?sr main.subbê??uéaP? ?I ?kxr   main.Data?????t÷?? ?I ?nxp   

有亂碼但是不影響我們觀察檩小,我們可以看到序列化時,不僅序列化了對象域的值烟勋,同時會指明序列化的這個對象是什么规求,也會指明繼承Serializable父類是什么,最后反序列化時總是反序列化為指明的對象
繼續(xù)思考卵惦,如果反序列時用Data接收阻肿,那么比較UID時是比較Data的UID和sub的UID還是sub的UID和sub的UID
一定是sub的UID,不然反序列就失敗了

Class文件可以序列化嗎

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement 

可以看到Class是繼承了Serializable接口的,毋庸置疑是可以的
異想天開一下沮尿,可不可以通過Class的序列化傳輸一個可序列化的類丛塌,然后傳輸這個類的對象,接受方反序列得到本來沒有的Class,然后通過這個Class反序列化這個類的對象
這里還是Data類

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"));
        out.writeObject(Data.class);
        Data d = new Data(10);
        out.writeObject(d);

首先把Data.class序列化,然后將Data的對象d也序列化
然后我們將Data注掉

//class Data implements Serializable{
//    private int n;
//    public Data(int n){
//        this.n = n;
//    }
//    public String toString(){
//        return Integer.toString(n);
//    }
//}

現(xiàn)在嘗試接受Data.class和Data的對象

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"));
        Class date = (Class) in.readObject();
        in.readObject();
Exception in thread "main" java.lang.ClassNotFoundException: main.sub
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:628)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1620)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
    at main.Worm.main(Worm.java:76)

發(fā)現(xiàn)是不能序列化的姨伤,這里筆者回頭看了一下Class文件的域哨坪,發(fā)現(xiàn)大多數(shù)都是static和transient,因此序列化Class對象沒有什么意義
那我們就不能通過網(wǎng)絡(luò)傳輸Class了嗎
其實是可以的乍楚,但是需要借助自定義的ClassLoader当编,通過socket傳輸class的字節(jié)碼文件,委托給自定義ClassLoader加載徒溪,就能夠?qū)崿F(xiàn)遠(yuǎn)程類加載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末忿偷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子臊泌,更是在濱河造成了極大的恐慌鲤桥,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,126評論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渠概,死亡現(xiàn)場離奇詭異茶凳,居然都是意外死亡,警方通過查閱死者的電腦和手機播揪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評論 3 400
  • 文/潘曉璐 我一進店門贮喧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人猪狈,你說我怎么就攤上這事箱沦。” “怎么了雇庙?”我有些...
    開封第一講書人閱讀 169,941評論 0 366
  • 文/不壞的土叔 我叫張陵谓形,是天一觀的道長。 經(jīng)常有香客問我疆前,道長寒跳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,294評論 1 300
  • 正文 為了忘掉前任峡继,我火速辦了婚禮冯袍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘碾牌。我一直安慰自己康愤,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,295評論 6 398
  • 文/花漫 我一把揭開白布舶吗。 她就那樣靜靜地躺著征冷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪誓琼。 梳的紋絲不亂的頭發(fā)上检激,一...
    開封第一講書人閱讀 52,874評論 1 314
  • 那天肴捉,我揣著相機與錄音,去河邊找鬼叔收。 笑死齿穗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的饺律。 我是一名探鬼主播窃页,決...
    沈念sama閱讀 41,285評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼复濒!你這毒婦竟也來了脖卖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,249評論 0 277
  • 序言:老撾萬榮一對情侶失蹤巧颈,失蹤者是張志新(化名)和其女友劉穎畦木,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砸泛,經(jīng)...
    沈念sama閱讀 46,760評論 1 321
  • 正文 獨居荒郊野嶺守林人離奇死亡十籍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,840評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了唇礁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妓雾。...
    茶點故事閱讀 40,973評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖垒迂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妒蛇,我是刑警寧澤机断,帶...
    沈念sama閱讀 36,631評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站绣夺,受9級特大地震影響吏奸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜陶耍,卻給世界環(huán)境...
    茶點故事閱讀 42,315評論 3 336
  • 文/蒙蒙 一奋蔚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧烈钞,春花似錦泊碑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至酗钞,卻和暖如春腹忽,著一層夾襖步出監(jiān)牢的瞬間来累,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評論 1 275
  • 我被黑心中介騙來泰國打工窘奏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嘹锁,地道東北人。 一個月前我還...
    沈念sama閱讀 49,431評論 3 379
  • 正文 我出身青樓着裹,卻偏偏與公主長得像领猾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子求冷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,982評論 2 361