java中的transient關鍵字詳解

前言 說實話學了一段時間java的朋友對於transient這個關鍵字依舊很陌生基本沒怎麼用過,可是transient關鍵字在java中卻起到了不可或缺的地位!若是要說講到,我以爲最可能出現的地方是IO流中對象流(也叫序列化流)的時候會講到!javascript

相信不少人都是直到本身碰到纔會關心這個關鍵字,記得博主第一次碰到transient關鍵字是在閱讀JDK源碼的時候。在學習java的過程當中transient關鍵字少見的緣由其實離不開它的做用:transient關鍵字的主要做用就是讓某些被transient關鍵字修飾的成員屬性變量不被序列化。實際上也正是所以,在學習過程當中不多用得上序列化操做,通常都是在實際開發中!至於序列化,相信有不少小白童鞋一直迷迷糊糊或者沒有具體的概念,這都不是事,下面博主會很清楚的讓你記住啥是序列化,保證你這輩子忘不了(貌似有點誇張,有點裝b,感受要被打)java

@[toc]程序員

一、何謂序列化?

提及序列化,隨之而來的另外一個概念就是反序列化,小白童鞋不要慌,記住了序列化就至關於記住了反序列化,由於反序列化就是序列化反過來,因此博主建議只記住序列化概念便可,省的搞暈本身。數據庫

專業術語定義的序列化:緩存

Java提供了一種對象序列化的機制。用一個字節序列能夠表示一個對象,該字節序列包含該對象的數據、對象的類型和對象中存儲的屬性等信息。字節序列寫出到文件以後,至關於文件中持久保存了一個對象的信息。反之,該字節序列還能夠從文件中讀取回來,重構對象,對它進行反序列化。對象的數據、對象的類型和對象中存儲的數據信息,均可以用來在內存中建立對象。安全

宜春的術語定義序列化:網絡

序列化: 字節 ——> 對象ide

其實,我總結的就是上面的結論,若是不理解,直接參照專業術語的定義,理解以後就記住個人話就好了,記不住,請打死我(我踢m簡直就是個天才)學習

圖理解序列化: 優化

在這裏插入圖片描述
啥?你不懂啥是字節?其實,我在一篇IO流的文章裏就已經介紹了序列化,放心,絕對特別詳細~光看文章名字就知道了~

史上最騷最全最詳細的IO流教程,小白都能看懂!

二、爲什麼要序列化?

從上一節提到序列化的概念,知道概念以後,咱們就必需要知道 爲什麼要序列化了。

講爲什麼要序列化緣由以前,博主我舉個栗子:

就像你去街上買菜,通常操做都是用塑料袋給包裝起來,直到回家要作菜的時候就把菜給拿出來。而這一系列操做就像極了序列化和反序列化!

Java中對象的序列化指的是將對象轉換成以字節序列的形式來表示,這些字節序列包含了對象的數據和信息,一個序列化後的對象 能夠被寫到數據庫或文件中,也可用於 網絡傳輸,通常當咱們使用 緩存cache(內存空間不夠有可能會本地存儲到硬盤)或 遠程調用rpc(網絡傳輸)的時候,常常須要讓咱們的實體類實現Serializable接口,目的就是爲了讓其可序列化。

  • 在開發過程當中要使用transient關鍵字修飾的栗子:

若是一個用戶有一些密碼等信息,爲了安全起見,不但願在網絡操做中被傳輸,這些信息對應的變量就能夠加上transient關鍵字。換句話說,這個字段的生命週期僅存於調用者的內存中而不會寫到磁盤裏持久化。

  • 在開發過程當中不須要transient關鍵字修飾的栗子:

一、類中的字段值能夠根據其它字段推導出來。 二、看具體業務需求,哪些字段不想被序列化;

不知道各位有木有想過爲何要不被序列化呢?其實主要是爲了節省存儲空間。優化程序!

PS:記得以前看HashMap源碼的時候,發現有個字段是用transient修飾的,我以爲仍是有道理的,確實不必對這個modCount字段進行序列化,由於沒有意義,modCount主要用於判斷HashMap是否被修改(像put、remove操做的時候,modCount都會自增),對於這種變量,一開始能夠爲任何值,0固然也是能夠(new出來、反序列化出來、或者克隆clone出來的時候都是爲0的),不必持久化其值。

固然,序列化後的最終目的是爲了反序列化,恢復成原先的Java對象,要否則序列化後幹嗎呢,就像買菜同樣,用塑料袋包裹最後仍是爲了方便安全到家再去掉塑料袋,因此序列化後的字節序列都是能夠恢復成Java對象的,這個過程就是反序列化。

三、序列化與transient的使用

 一、須要作序列化的對象的類,必須實現序列化接口:Java.lang.Serializable 接口(一個標誌接口,沒有任何抽象方法),Java 中大多數類都實現了該接口,好比:StringInteger類等,不實現此接口的類將不會使任何狀態序列化或反序列化,會拋NotSerializableException異常 。

  二、底層會判斷,若是當前對象是 Serializable 的實例,才容許作序列化,Java對象 instanceof Serializable 來判斷。

  三、在 Java 中使用對象流ObjectOutputStream來完成序列化以及ObjectInputStream流反序列化   

  1. ==ObjectOutputStream:經過 writeObject()方法作序列化操做== 

  2. ==ObjectInputStream:經過 readObject() 方法作反序列化操做==

四、該類的全部屬性必須是可序列化的。若是有一個屬性不須要可序列化的,則該屬性必須註明是瞬態的,使用transient 關鍵字修飾。

在這裏插入圖片描述
因爲字節嘛因此確定要涉及流的操做,也就是對象流也叫序列化流ObjectOutputstream,下面進行多種狀況分析序列化的操做代碼!

在這裏,我真的強烈建議看宜春博客的讀者朋友,請試着去敲,切記一眼帶過或者複製過去運行就完事了,特別是小白童鞋,相信我!你必定會有不同的收穫。千萬不要以爲浪費時間,有時候慢就是快,宜春親身體會!

3.一、沒有實現Serializable接口進行序列化狀況

package TransientTest;
import java.io.*;

class UserInfo {  //================================注意這裏沒有實現Serializable接口
    private String name;
    private transient String password;

    public UserInfo(String name,String psw) {
        this.name = name;
        this.password=psw;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

public class TransientDemo {
    public static void main(String[] args) {

        UserInfo userInfo=new UserInfo("老王","123");
        System.out.println("序列化以前信息:"+userInfo);

        try {
            ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt"));
            output.writeObject(new UserInfo("老王","123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

運行結果

在這裏插入圖片描述

3.二、實現Serializable接口序列化狀況

當咱們加上實現Serializable接口再運行會發現,項目中出現的userinfo.txt文件內容是這樣的:

在這裏插入圖片描述
其實這都不是重點,重點是序列化操做成功了!

3.三、普通序列化狀況

package TransientTest;
import java.io.*;

class UserInfo implements Serializable{  //第一步實現Serializable接口
    private String name;
    private String password;//都是普通屬性==============================

    public UserInfo(String name,String psw) {
        this.name = name;
        this.password=psw;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

public class TransientDemo {
    public static void main(String[] args) throws ClassNotFoundException {

        UserInfo userInfo=new UserInfo("程序員老王","123");
        System.out.println("序列化以前信息:"+userInfo);

        try {
            ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步開始序列化操做
            output.writeObject(new UserInfo("程序員老王","123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream input=new ObjectInputStream(new FileInputStream("userinfo.txt"));//第三步開始反序列化操做
            Object o = input.readObject();//ObjectInputStream的readObject方法會拋出ClassNotFoundException
            System.out.println("序列化以後信息:"+o);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

運行結果:

序列化以前信息:UserInfo{name='程序員老王', password='123'}
序列化以後信息:UserInfo{name='程序員老王', password='123'}
複製代碼

3.四、transient序列化狀況

package TransientTest;
import java.io.*;

class UserInfo implements Serializable{  //第一步實現Serializable接口
    private String name;
    private transient String password; //特別注意:屬性由transient關鍵字修飾===========

    public UserInfo(String name,String psw) {
        this.name = name;
        this.password=psw;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

public class TransientDemo {
    public static void main(String[] args) throws ClassNotFoundException {

        UserInfo userInfo=new UserInfo("程序員老王","123");
        System.out.println("序列化以前信息:"+userInfo);

        try {
            ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步開始序列化操做
            output.writeObject(new UserInfo("程序員老王","123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream input=new ObjectInputStream(new FileInputStream("userinfo.txt"));//第三步開始反序列化操做
            Object o = input.readObject();//ObjectInputStream的readObject方法會拋出ClassNotFoundException
            System.out.println("序列化以後信息:"+o);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

運行結果:

序列化以前信息:UserInfo{name='程序員老王', password='123'}
序列化以後信息:UserInfo{name='程序員老王', password='null'}
複製代碼

特別注意結果,添加transient修飾的屬性值爲默認值null!若是被transient修飾的屬性爲int類型,那它被序列化以後值必定是0,固然各位能夠去試試,這能說明什麼呢?說明被標記爲transient的屬性在對象被序列化的時候不會被保存(或者說變量不會持久化)

3.五、static序列化狀況

package TransientTest;
import java.io.*;

class UserInfo implements Serializable{  //第一步實現Serializable接口
    private String name;
    private static String password; //特別注意:屬性由static關鍵字修飾==============

    public UserInfo(String name, String psw) {
        this.name = name;
        this.password=psw;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

public class TransientDemo {
    public static void main(String[] args) throws ClassNotFoundException {

        UserInfo userInfo=new UserInfo("程序員老王","123");
        System.out.println("序列化以前信息:"+userInfo);

        try {
            ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步開始序列化操做
            output.writeObject(new UserInfo("程序員老王","123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream input=new ObjectInputStream(new FileInputStream("userinfo.txt"));//第三步開始反序列化操做
            Object o = input.readObject();//ObjectInputStream的readObject方法會拋出ClassNotFoundException
            System.out.println("序列化以後信息:"+o);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

運行結果:

序列化以前信息:UserInfo{name='程序員老王', password='123'}
序列化以後信息:UserInfo{name='程序員老王', password='123'}
複製代碼

這個時候,你就會錯誤的認爲static修飾的也被序列化了,其實否則,實際上這裏很容易被搞暈!明明取出null(默認值)就能夠說明不會被序列化,這裏明明沒有變成默認值,爲什麼還要說static不會被序列化呢?

實際上,反序列化後類中static型變量name的值其實是當前JVM中對應static變量的值,這個值是JVM中的並非反序列化得出的。也就是說被static修飾的變量並無參與序列化!可是咱也不能口說無憑啊,是的,那咱們就來看兩個程序對比一下就明白了!

第一個程序:這是一個沒有被static修飾的name屬性程序:

package Thread;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class UserInfo implements Serializable {
    private String name;
    private transient String psw;

    public UserInfo(String name, String psw) {
        this.name = name;
        this.psw = psw;
    }

    public  String getName() {
        return name;
    }

    public  void setName(String name) {
        this.name = name;
    }

    public String getPsw() {
        return psw;
    }

    public void setPsw(String psw) {
        this.psw = psw;
    }

    public String toString() {
        return "name=" + name + ", psw=" + psw;
    }
}
public class TestTransient {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo("程序員老過", "456");
        System.out.println(userInfo);
        try {
            // 序列化,被設置爲transient的屬性沒有被序列化
            ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.txt"));
            o.writeObject(userInfo);
            o.close();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        try {
            //在反序列化以前改變name的值 =================================注意這裏的代碼
            userInfo.setName("程序員老改");
            // 從新讀取內容
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.txt"));
            UserInfo readUserInfo = (UserInfo) in.readObject();
            //讀取後psw的內容爲null
            System.out.println(readUserInfo.toString());
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}
複製代碼

運行結果:

name=程序員老過, psw=456
name=程序員老過, psw=null
複製代碼

從程序運行結果中能夠看出,在反序列化以前試着改變name的值爲程序員老改,結果是沒有成功的!

第二個程序:這是一個被static修飾的name屬性程序:

package Thread;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class UserInfo implements Serializable {
    private static final long serialVersionUID = 996890129747019948L;
    private static String name;
    private transient String psw;

    public UserInfo(String name, String psw) {
        this.name = name;
        this.psw = psw;
    }

    public  String getName() {
        return name;
    }

    public  void setName(String name) {
        this.name = name;
    }

    public String getPsw() {
        return psw;
    }

    public void setPsw(String psw) {
        this.psw = psw;
    }

    public String toString() {
        return "name=" + name + ", psw=" + psw;
    }
}
public class TestTransient {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo("程序員老過", "456");
        System.out.println(userInfo);
        try {
            // 序列化,被設置爲transient的屬性沒有被序列化
            ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.txt"));
            o.writeObject(userInfo);
            o.close();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        try {
            //在反序列化以前改變name的值
            userInfo.setName("程序員老改");
            // 從新讀取內容
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.txt"));
            UserInfo readUserInfo = (UserInfo) in.readObject();
            //讀取後psw的內容爲null
            System.out.println(readUserInfo.toString());
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}
複製代碼

運行結果:

name=程序員老過, psw=456
name=程序員老改, psw=null
複製代碼

從程序運行結果中能夠看出,在反序列化以前試着改變name的值爲程序員老改,結果是成功的!如今對比一下兩個程序是否是就很清晰了?

static關鍵字修飾的成員屬性優於非靜態成員屬性加載到內存中,同時靜態也優於對象進入到內存中,被static修飾的成員變量不能被序列化,序列化的都是對象,靜態變量不是對象狀態的一部分,所以它不參與序列化。因此將靜態變量聲明爲transient變量是沒有用處的。所以,反序列化後類中static型變量name的值其實是當前JVM中對應static變量的值,這個值是JVM中的並非反序列化得出的。

若是對static關鍵字仍是不太清楚理解的童鞋能夠參考這篇文章,應該算是不錯的:深刻理解static關鍵字

3.六、final序列化狀況

對於final關鍵字來說,final變量將直接經過值參與序列化,至於代碼程序我就再也不貼出來了,你們能夠試着用final修飾驗證一下!

主要注意的是final 和transient能夠同時修飾同一個變量,結果也是同樣的,對transient沒有影響,這裏主要提一下,但願各位之後在開發中遇到這些狀況不會滿頭霧水!

四、java類中serialVersionUID做用

既然提到了transient關鍵字就不得不提到序列化,既然提到了序列化,就不得不提到serialVersionUID了,它是啥呢?基本上有序列化就會存在這個serialVersionUID。

在這裏插入圖片描述
serialVersionUID適用於Java的序列化機制。簡單來講,Java的序列化機制是經過判斷類的 serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的 serialVersionUID與本地相應實體類的 serialVersionUID進行比較,若是相同就認爲是一致的,能夠進行反序列化,不然就會出現序列化版本不一致的異常,便是 InvalidCastException,在開發中有時候可寫可不寫,建議最好仍是寫上比較好。

五、transient關鍵字小結

一、變量被transient修飾,變量將不會被序列化 二、transient關鍵字只能修飾變量,而不能修飾方法和類。 三、被static關鍵字修飾的變量不參與序列化,一個靜態static變量無論是否被transient修飾,均不能被序列化。 四、final變量值參與序列化,final transient同時修飾變量,final不會影響transient,同樣不會參與序列化

第二點須要注意的是:本地變量是不能被transient關鍵字修飾的。變量若是是用戶自定義類變量,則該類須要實現Serializable接口

第三點須要注意的是:反序列化後類中static型變量的值其實是當前JVM中對應static變量的值,這個值是JVM中的並非反序列化得出的。

結語:被transient關鍵字修飾致使不被序列化,其優勢是能夠節省存儲空間。優化程序!隨之而來的是會致使被transient修飾的字段會從新計算,初始化!

若是本文對你有一點點幫助,那麼請點個讚唄,謝謝~

如有不足或者不正之處,歡迎指正批評,感激涕零!若是有疑問歡迎留言,絕對第一時間回覆!

最後,歡迎各位關注宜春的公衆號,一塊兒探討技術,嚮往技術,追求技術,說好了來了就是盆友喔...

在這裏插入圖片描述
  
相關文章
相關標籤/搜索