首頁 Java Java基礎 詳解java中的transient關鍵字

詳解java中的transient關鍵字

Nov 26, 2019 pm 04:14 PM
java transient

說實話學了一段時間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中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

突破或從Java 8流返回? 突破或從Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP與Python:了解差異 PHP與Python:了解差異 Apr 11, 2025 am 12:15 AM

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

PHP與其他語言:比較 PHP與其他語言:比較 Apr 13, 2025 am 12:19 AM

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

Java程序查找膠囊的體積 Java程序查找膠囊的體積 Feb 07, 2025 am 11:37 AM

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4

PHP與Python:核心功能 PHP與Python:核心功能 Apr 13, 2025 am 12:16 AM

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

創造未來:零基礎的 Java 編程 創造未來:零基礎的 Java 編程 Oct 13, 2024 pm 01:32 PM

Java是熱門程式語言,適合初學者和經驗豐富的開發者學習。本教學從基礎概念出發,逐步深入解說進階主題。安裝Java開發工具包後,可透過建立簡單的「Hello,World!」程式來實踐程式設計。理解程式碼後,使用命令提示字元編譯並執行程序,控制台上將輸出「Hello,World!」。學習Java開啟了程式設計之旅,隨著掌握程度加深,可創建更複雜的應用程式。

PHP:許多網站的基礎 PHP:許多網站的基礎 Apr 13, 2025 am 12:07 AM

PHP成為許多網站首選技術棧的原因包括其易用性、強大社區支持和廣泛應用。 1)易於學習和使用,適合初學者。 2)擁有龐大的開發者社區,資源豐富。 3)廣泛應用於WordPress、Drupal等平台。 4)與Web服務器緊密集成,簡化開發部署。

See all articles