Java序列化

Java序列化

1. 什么是序列化陡鹃?

序列化是將一個對象的狀態(tài)磷蛹,各屬性的值序列化保存起來吮旅,然后在合適的時候通過反序列化獲得。

Java的序列化是將一個對象表示成字節(jié)序列,該字節(jié)序列包括了對象的數(shù)據(jù)庇勃,有關對象的類型信息和存儲在對象中的數(shù)據(jù)類型檬嘀。

說白了,就是將對象保存起來责嚷,就跟保存字符串數(shù)據(jù)一樣鸳兽,用到的時候再取出來。任何實現(xiàn)了Serializable接口的類都可以被序列化罕拂。

2. 實現(xiàn)Serializable接口進行序列化

package com.wangjun.othersOfJava;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SerializeDemo {

    public static void main(String[] args) {
        Employee em = new Employee();
        em.name = "wangjun";
        em.age = 24;
        em.ssh = 123456;
        // 將對象序列化后保存到文件
        try (
                FileOutputStream fo = new FileOutputStream("tem.ser");
                ObjectOutputStream oo = new ObjectOutputStream(fo))
        {
            oo.writeObject(em);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 反序列化取出對象
        try(
                FileInputStream fi = new FileInputStream("tem.ser");
                ObjectInputStream oi = new ObjectInputStream(fi)) 
        {
            Employee e2 = (Employee) oi.readObject();
            System.out.println(e2.name);
            System.out.println(e2.age);
            System.out.println(e2.ssh);
            System.out.println(Employee.local);
            e2.test();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static class Employee implements Serializable {
        String name;
        int age;
        static String local = "earth";
        transient int ssh;

        public void test() {
            System.out.println("this is test method!");
        }
    }

}

程序的運行結果:

wangjun
24
0
earth
this is test method!

如果有一些字段不想被序列化怎么辦呢揍异?這時候就可以用transient關鍵字修飾,就像上面代碼的ssh字段爆班,關于transient關鍵字有以下幾個特點:

  • 一旦被transient關鍵字修飾衷掷,那變量將不再是對象持久化的一部分,該變量內(nèi)容在序列化后無法獲得訪問柿菩;
  • transient只能修飾變量戚嗅,不能修飾方法和類,本地變量(局部變量)也不能被transient修飾枢舶;
  • 一個靜態(tài)變量不管是否被transient修飾渡处,都不能被序列化。

從上面的例子看到好像與第三條不符祟辟,其實反序列化取出的local是JVM里面的值医瘫,而不是反序列化出來的【衫В可以加一行代碼驗證一下醇份,在反序列化之前更改一下local的值:

// 反序列化取出對象
Employee.local = "earth2";
try(
  ...

看一下打印結果

wangjun
24
0
earth2
this is test method!

這說明打印出來的是JVM中對應的local的值earth2,而不是序列化的時候的值earth吼具。

3. 實現(xiàn)Externalizable接口進行序列化

transient只有對實現(xiàn)了Serializable接口方式的序列化有效僚纷,還有一種序列化的方式是實現(xiàn)Externalizable接口,這種實現(xiàn)方式不像實現(xiàn)Serializable接口一樣可以幫你自動序列化拗盒,它需要在writeExternal方法中手動指定需要序列化的變量并且在readExternal手動取出來怖竭,這與是否被transient修飾無關,下面更改一下上面的例子陡蝇,將Employee類改成:

static class Employee implements Externalizable {
        String name;
        int age;
        static String local = "earth";
        transient int ssh;
        
        //實現(xiàn)Externalizable接口進行序列化必須顯式聲明無參構造器
        public Employee() {
        }

        public void test() {
            System.out.println("this is test method!");
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(name);
            //out.writeObject(age);
            out.writeObject(ssh);
            out.writeObject(local);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            name = (String) in.readObject();
            //age = (int) in.readObject();
            ssh = (int) in.readObject();
            local = (String) in.readObject();
        }
    }

重新運行痊臭,結果(注意:上述主函數(shù)中還存在對local重新賦值的代碼Employee.local = "earth2";):

wangjun
0
123456
earth
this is test method!

可以看到能否被序列化跟transient和static修飾都沒有關系,只跟writeExternal和readExternal有關系登夫。

4. Serializable和Externalizable的區(qū)別

  • 對Serializable對象反序列化時广匙,由于Serializable對象完全以它存儲的二進制位為基礎來構造,因此并不會調(diào)用任何構造函數(shù)恼策,因此Serializable類無需默認構造函數(shù)鸦致,但是當Serializable類的父類沒有實現(xiàn)Serializable接口時,反序列化過程會調(diào)用父類的默認構造函數(shù),因此該父類必需有默認構造函數(shù)分唾,否則會拋異常抗碰。
  • 對Externalizable對象反序列化時,會先調(diào)用類的不帶參數(shù)的構造方法绽乔,這是有別于默認反序列方式的改含。如果把類的不帶參數(shù)的構造方法刪除,或者把該構造方法的訪問權限設置為private迄汛、默認或protected級別捍壤,會拋出java.io.InvalidException: no valid constructor異常,因此Externalizable對象必須有默認構造函數(shù)鞍爱,而且必需是public的鹃觉。
  • 如果不是特別堅持實現(xiàn)Externalizable接口,那么還有另一種方法睹逃。我們可以實現(xiàn)Serializable接口盗扇,并添加writeObject()readObject()的方法。一旦對象被序列化或者重新裝配沉填,就會分別調(diào)用那兩個方法疗隶。也就是說,只要提供了這兩個方法翼闹,就會優(yōu)先使用它們斑鼻,而不考慮默認的序列化機制。

5. SerialVersionUID的作用

上述實現(xiàn)Serializable接口的Employee類中猎荠,會有一個警告:

The serializable class Employee does not declare a static final serialVersionUID field of type long

意思是Employee沒有聲明一個靜態(tài)final的常量serialVersionUID坚弱,那這個serialVersionUID的作用是什么呢?

serialVersionUID是對類進行版本控制的关摇,Java的序列化機制是通過判斷類的serialVersionUID來驗證版本一致性的荒叶。在進行反序列化時,JVM會把傳來的字節(jié)流中的serialVersionUID與本地相應實體類的serialVersionUID進行比較输虱,如果相同就認為是一致的些楣,可以進行反序列化,否則就會出現(xiàn)序列化版本不一致的異常宪睹,即是InvalidCastException愁茁。

serialVersionUID有兩種生成方式:

  • 一是默認的1L,比如:private static final long serialVersionUID = 1L横堡;
  • 二是根據(jù)類名埋市、接口名冠桃、成員方法及屬性等來生成一個64位的哈希字段命贴。

如果程序沒有顯式的聲明serialVersionUID,那么程序將用第二種實現(xiàn)。我們可以做一個實現(xiàn)胸蛛,還是用上述實現(xiàn)Serializable接口的例子污茵。

我們先運行一下程序,生成序列化文件tem.ser葬项,在把“將對象序列化后保存到文件”這一段邏輯注釋掉泞当,對Employee類增加一個test字段:

static class Employee implements Serializable {
  String name;
  int age;
  static String local = "earth";
  transient int ssh;
  String test;

  public void test() {
    System.out.println("this is test method!");
  }
}

這時候運行的時候會報錯:

java.io.InvalidClassException: com.wangjun.othersOfJava.SerializeDemo$Employee; local class incompatible: stream classdesc serialVersionUID = 4506166831890198488, local class serialVersionUID = 785960679919880606
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1843)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2000)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
    at com.wangjun.othersOfJava.SerializeDemo.main(SerializeDemo.java:32)

因為程序發(fā)現(xiàn)取到的序列化文件的serialVersionUID和當前的serialVersionUID不一樣。這個serialVersionUID是根據(jù)類名民珍、接口名襟士、成員方法及屬性等來生成一個64位的哈希字段,因為增加了test字段嚷量,因此生成的serialVersionUID不一樣了陋桂。

接著,我們顯式的聲明serialVersionUID

static class Employee implements Serializable {
  private static final long serialVersionUID = 1L;
  String name;
  int age;
  static String local = "earth";
  transient int ssh;

  public void test() {
    System.out.println("this is test method!");
  }
}

將剛才注釋的代碼取消注釋蝶溶,運行一遍再注釋掉嗜历,并且新增字段test:

static class Employee implements Serializable {
  private static final long serialVersionUID = 1L;
  String name;
  int age;
  static String local = "earth";
  transient int ssh;
  String test;

  public void test() {
    System.out.println("this is test method!");
  }
}

再次運行發(fā)現(xiàn)沒有報錯,運行OK抖所。這是因為你顯式聲明了serialVersionUID梨州,序列化的serialVersionUID和目前的serialVersionUID一樣,因此會認為是同一個版本的類田轧。

你也可以將serialVersionUID改成2L暴匠,這個時候又會報錯了。

參考:

https://www.cnblogs.com/duanxz/p/3511695.html

https://blog.csdn.net/fjndwy/article/details/39374231

https://blog.csdn.net/bigtree_3721/article/details/50513295

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末傻粘,一起剝皮案震驚了整個濱河市巷查,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抹腿,老刑警劉巖岛请,帶你破解...
    沈念sama閱讀 222,865評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異警绩,居然都是意外死亡崇败,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評論 3 399
  • 文/潘曉璐 我一進店門肩祥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來后室,“玉大人,你說我怎么就攤上這事混狠“杜” “怎么了?”我有些...
    開封第一講書人閱讀 169,631評論 0 364
  • 文/不壞的土叔 我叫張陵将饺,是天一觀的道長贡避。 經(jīng)常有香客問我痛黎,道長,這世上最難降的妖魔是什么刮吧? 我笑而不...
    開封第一講書人閱讀 60,199評論 1 300
  • 正文 為了忘掉前任湖饱,我火速辦了婚禮,結果婚禮上杀捻,老公的妹妹穿的比我還像新娘井厌。我一直安慰自己,他們只是感情好致讥,可當我...
    茶點故事閱讀 69,196評論 6 398
  • 文/花漫 我一把揭開白布仅仆。 她就那樣靜靜地躺著,像睡著了一般垢袱。 火紅的嫁衣襯著肌膚如雪蝇恶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,793評論 1 314
  • 那天惶桐,我揣著相機與錄音撮弧,去河邊找鬼。 笑死姚糊,一個胖子當著我的面吹牛贿衍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播救恨,決...
    沈念sama閱讀 41,221評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼贸辈,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了肠槽?” 一聲冷哼從身側響起擎淤,我...
    開封第一講書人閱讀 40,174評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎秸仙,沒想到半個月后嘴拢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,699評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡寂纪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,770評論 3 343
  • 正文 我和宋清朗相戀三年席吴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捞蛋。...
    茶點故事閱讀 40,918評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡孝冒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拟杉,到底是詐尸還是另有隱情庄涡,我是刑警寧澤,帶...
    沈念sama閱讀 36,573評論 5 351
  • 正文 年R本政府宣布搬设,位于F島的核電站穴店,受9級特大地震影響撕捍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜迹鹅,卻給世界環(huán)境...
    茶點故事閱讀 42,255評論 3 336
  • 文/蒙蒙 一卦洽、第九天 我趴在偏房一處隱蔽的房頂上張望贞言。 院中可真熱鬧斜棚,春花似錦、人聲如沸该窗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酗失。三九已至义钉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間规肴,已是汗流浹背捶闸。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拖刃,地道東北人删壮。 一個月前我還...
    沈念sama閱讀 49,364評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像兑牡,于是被迫代替她去往敵國和親央碟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,926評論 2 361

推薦閱讀更多精彩內(nèi)容

  • Java序列化機制 序列化和反序列化 Java序列化是Java內(nèi)建的數(shù)據(jù)(對象)持久化機制均函,通過序列化可以將運行時...
    0x70e8閱讀 470評論 0 1
  • 1.背景 某天亿虽,我在寫代碼定義 bean 的時候,順手寫了個 public class User implemen...
    李眼鏡閱讀 785評論 0 2
  • 如果你只知道實現(xiàn) Serializable 接口的對象苞也,可以序列化為本地文件洛勉。那你最好再閱讀該篇文章,文章對序列化...
    jiangmo閱讀 481評論 0 2
  • 正如前文《Java序列化心得(一):序列化設計和默認序列化格式的問題》中所提到的如迟,默認序列化方法存在各種各樣的問題...
    登高且賦閱讀 8,428評論 0 19
  • 題外話:從事IT要學習的東西太多了坯认,有時候會比較浮躁,因為要學的東西太多但又無從下手氓涣,甚至有很多基礎都還沒有深入學...
    Garwer閱讀 761評論 2 9