首頁  >  文章  >  Java  >  詳解java中的transient關鍵字

詳解java中的transient關鍵字

angryTom
angryTom轉載
2019-11-26 16:14:581728瀏覽

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

詳解java中的transient關鍵字

相信很多人都是直到自己碰到才會關心這個關鍵字,記得部落客第一次碰到transient關鍵字是在閱讀JDK原始碼的時候。在學習java的過程中transient關鍵字少見的原因其實離不開它的作用:transient關鍵字的主要作用就是讓某些被transient關鍵字修飾的成員屬性變數不被序列化。實際上也正是因此,在學習過程中很少用得上序列化操作,一般都是在實際開發中!至於序列化,相信有很多小白童鞋一直迷迷糊糊或沒有具體的概念,這都不是事,下面博主會很清楚的讓你記住啥是序列化,保證你這輩子忘不了(貌似有點誇張,有點裝b,感覺要被打)

1、何謂序列化?

說起序列化,隨之而來的另一個概念就是反序列化,小白童鞋不要慌,記住了序列化就相當於記住了反序列化,因為反序列化就是序列化反過來,所以部落客建議只記得序列化概念即可,省的搞暈自己。

(推薦影片:java影片教學#)  

專業術語定義的序列化:

Java提供了一個物件序列化的機制。用一個位元組序列可以表示一個對象,該位元組序列包含該對象的資料、對象的類型和對像中儲存的屬性等資訊。位元組序列寫出到檔案之後,相當於檔案中持久保存了一個物件的資訊。反之,該位元組序列還可以從檔案中讀取回來,重構對象,對它進行反序列化。物件的資料、物件的類型和物件中儲存的資料訊息,都可以用來在記憶體中建立物件。

序列化: 位元組-> 物件

其實,我總結的就是上面的結論,如果不理解,直接參考專業術語的定義,理解之後就記住我的話就行了,記不住,請打死我

圖理解序列化:

詳解java中的transient關鍵字

2、為何要序列化?

從上一節提到序列化的概念,知道概念之後,我們就必須知道 為何要序列化了。

講為何要序列化原因之前,部落客我舉個栗子:

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

Java中物件的序列化指的是將物件轉換成以位元組序列的形式來表示,這些位元組序列包含了物件的資料和訊息,一個序列化後的物件可以被寫到資料庫或檔案中,也可用於網路傳輸,一般當我們使用快取cache(記憶體空間不夠有可能會本地儲存到硬碟)或遠端呼叫rpc(網路傳輸)的時候,經常需要讓我們的實體類別實作Serializable介面,目的就是為了讓其可序列化。

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

如果一個用戶有一些密碼等信息,為了安全起見,不希望在網絡操作中被傳輸,這些信息對應的變數就可以加上transient關鍵字。換句話說,這個欄位的生命週期僅存於呼叫者的記憶體中而不會寫到磁碟裡持久化。

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

#1、類別中的欄位值可以根據其它欄位推導出來。

2、看具體業務需求,哪些字段不想被序列化;

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

PS:記得之前看HashMap原始碼的時候,我發現有個欄位是用transient修飾的,我覺得還是合理的,確實沒必要對這個modCount欄位進行序列化,因為沒有意義,modCount主要用來判斷HashMap是否被修改(像put、remove操作的時候,modCount都會自增),對於這種變量,一開始可以為任何值,0當然也是可以(new出來、反序列化出來、或者克隆clone出來的時候都是0的),沒必要持久化其值。

當然,序列化後的最終目的是為了反序列化,恢復成原先的Java對象,要不然序列化後乾嘛呢,就像買菜一樣,用塑膠袋包裹最後還是為了方便安全到家再去掉塑膠袋,所以序列化後的位元組序列都是可以恢復成Java物件的,這個過程就是反序列化。

3、序列化與transient的使用

1、需要做序列化的物件的類,必須實現序列化介面:Java.lang.Serializable 介面(一個標誌接口,沒有任何抽象方法),Java 中大多數類別都實作了該接口,例如:String,Integer類別等,不實作此介面的類別將不會使任何狀態序列化或反序列化,會拋NotSerializableException異常。

2、底層會判斷,如果目前物件是 Serializable 的實例,才允許做序列化,Java物件 instanceof Serializable 來判斷。

3、在Java 中使用物件流ObjectOutputStream來完成序列化以及ObjectInputStream流反序列化

==ObjectOutputStream:透過writeObject()方法做序列化操作== 

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

4、該類別的所有屬性必須是可序列化的。如果有一個屬性不需要可序列化的,則該屬性必須註明是瞬態的,使用transient 關鍵字修飾。

詳解java中的transient關鍵字

由於位元組嘛所以肯定要涉及流的操作,也就是物件流也叫序列化流ObjectOutputstream,下面進行多種情況分析序列化的操作代碼!

在這裡,我真的強烈建議看宜春博客的讀者朋友,請試著去敲,切記一眼帶過或複製過去運行就完事了,特別是小白童鞋,相信我!你一定會有不一樣的收穫。 !

3.1、沒有實作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();
        }
    }
}

運行結果

詳解java中的transient關鍵字

3.2、實作Serializable介面序列化情況

當我們加上實作Serializable介面再運作會發現,專案中出現的userinfo.txt檔案內容是這樣的:

詳解java中的transient關鍵字

其實這都不是重點,重點是序列化操作成功了!

3.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.4、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.5、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=456name=程序员老过, 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 = 996890129747019948 L;
    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=456name=程序员老改, psw=null

從程式執行結果中可以看出,在反序列化之前試著改變name的值為程式設計師老改,結果是成功的!現在對比一下兩個程序是不是就很清楚了?

static關鍵字修飾的成員屬性優於非靜態成員屬性載入到記憶體中,同時靜態也優於物件進入記憶體中,被static修飾的成員變數不能被序列化,序列化的都是對象,靜態變數不是物件狀態的一部分,因此它不參與序列化。所以將靜態變數宣告為transient變數是沒有用處的。因此,反序列化後類別中static型變數name的值其實就是目前JVM中對應static變數的值,這個值是JVM中的並不是反序列化得出的。

3.6、final序列化情況

對於final關鍵字來講,final變數將直接透過值參與序列化,至於程式碼程式我就不再貼出來了,大家可以試著用final修飾驗證一下!

主要注意的是final 和transient可以同時修飾同一個變量,結果也是一樣的,對transient沒有影響,這裡主要提一下,希望各位以後在開發中遇到這些情況不會滿頭霧水!

4、java類別中serialVersionUID作用

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

詳解java中的transient關鍵字

serialVersionUID適用於Java的序列化機制。簡單來說,Java的序列化機制是透過判斷類別的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的位元組流中的serialVersionUID與本地對應實體類別的serialVersionUID進行比較,如果相同就認為是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常,即是InvalidCastException,在開發中有時候可寫可不寫,建議最好還是寫上比較好。

5、transient關鍵字小結

1、變數被transient修飾,變數將不會被序列化
2、transient關鍵字只能修飾變量,而不能修飾方法和類別。
3、被static關鍵字修飾的變數不參與序列化,一個靜態static變數不管是否被transient修飾,都不能被序列化。
4、final變數值參與序列化,final transient同時修飾變量,final不會影響transient,一樣不會參與序列化

第二點要注意的是:本地變數是不能被transient關鍵字修飾的。變數如果是使用者自訂類別變量,則該類別需要實作Serializable介面

第三點需要注意的是:反序列化後類別中static型變數的值實際上是目前JVM中對應static變量的值,這個值是JVM中的並不是反序列化得出的。

結語:被transient關鍵字修飾導致不被序列化,其優點是可以節省儲存空間。優化程序!隨之而來的是會導致被transient修飾的欄位會重新計算,初始化!

本文來自php中文網,java教學欄目,歡迎學習!  

以上是詳解java中的transient關鍵字的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:cnblogs.com。如有侵權,請聯絡admin@php.cn刪除