<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試長(zhǎng)知識(shí)了!Java 關(guān)鍵字 transient 盡然還能這么用

          共 5067字,需瀏覽 11分鐘

           ·

          2021-05-15 20:20


          前言

          最近在看 HashMap 源代碼的時(shí)候,發(fā)現(xiàn)鏈表 table 數(shù)組采用了transient 關(guān)鍵字,筆者當(dāng)時(shí)感覺對(duì) transient 關(guān)鍵字即陌生但又有似曾相識(shí),所以花了一些時(shí)間簡(jiǎn)要的總結(jié)了下使用transient 關(guān)鍵字的一些基本常識(shí),希望對(duì)你們也有些幫助,讓我們一起進(jìn)步,一起牛逼吧。

          transient 關(guān)鍵字的定義

          說起 transient 關(guān)鍵字,不得不提對(duì)象的 序列化 的,因?yàn)槲覀兂3P枰诰W(wǎng)絡(luò)上以對(duì)象(數(shù)據(jù))的二進(jìn)制方式傳輸數(shù)據(jù),這里涉及到發(fā)送方序列化對(duì)象,接收方反序列化對(duì)象的過程。

          那什么是序列化/反序列化?

          Java 中對(duì)象的序列化指的是將對(duì)象轉(zhuǎn)換成以字節(jié)序列的形式來表示,這些字節(jié)序列包含了對(duì)象的數(shù)據(jù)和信息,一個(gè)序列化后的對(duì)象可以被寫到數(shù)據(jù)庫或文件中,也可用于網(wǎng)絡(luò)傳輸。一般地,當(dāng)我們使用緩存 cache(內(nèi)存空間不夠有可能會(huì)本地存儲(chǔ)到硬盤)或遠(yuǎn)程調(diào)用 rpc(網(wǎng)絡(luò)傳輸)的時(shí)候,經(jīng)常需要讓實(shí)體類實(shí)現(xiàn) Serializable 接口,目的就是為了讓其可序列化。當(dāng)然,序列化后的最終目的是為了反序列化,恢復(fù)成原先的Java對(duì)象實(shí)例。所以序列化后的字節(jié)序列都是可以恢復(fù)成Java對(duì)象的,這個(gè)過程就是反序列化。

          在對(duì)象的序列化/反序列化過程中,我們經(jīng)常有這種需求,就是非必要字段不必進(jìn)行序列化。

          例如有一個(gè)對(duì)象有三個(gè)字段 field1、field2、field3,發(fā)送方不想讓字段 field3 被序列化,因?yàn)檫@里面可能涉及到一些敏感信息不想被接收方知道,那有沒有辦法解決這個(gè)問題呢?

          其實(shí)聰明的 Java 作者早就為我們量身定做了 transient 關(guān)鍵字!

          簡(jiǎn)單來說,被 transient 關(guān)鍵字修飾過的成員屬性不能被序列化,transient 關(guān)鍵字只能修飾變量,而不能修飾方法和類。

          transient 關(guān)鍵字的約定

          • 約定一、只能修飾變量而不能修飾方法和類。注意本地變量是不能被 transient 關(guān)鍵字修飾的。

          • 約定二、被 transient 關(guān)鍵字修飾過的屬性不能被序列化,也就是說被 transient 修飾過的屬性,在對(duì)對(duì)象序列化后,是無法訪問到該屬性的。

          • 約定三、靜態(tài)變量不管有無被 transient 修飾過,不能被序列化。

          transient 關(guān)鍵字的使用場(chǎng)景

          首先,我們看個(gè)例子,有個(gè)產(chǎn)品對(duì)象 Product,包括價(jià)格、數(shù)量、總價(jià)三個(gè)字段,那么總價(jià)可以通過 價(jià)格 乘以 數(shù)量 推導(dǎo)出來。

          我們以查詢某個(gè)產(chǎn)品 API 接口為例,通過產(chǎn)品 ID,查詢返回一個(gè)產(chǎn)品對(duì)象。

          public class Product {
           private int amounts;
           private int price;
           private int sum;
          }

          通過 Gson 序列化后把 json 數(shù)據(jù)返回給前端,這時(shí)的 sum 字段是沒有經(jīng)過 transient 修飾過的,所以能夠正常序列化。

          {"amounts":3,"price":2,"sum":6}

          假設(shè)我們的產(chǎn)品對(duì)象 Productsum 屬性加上 transient 關(guān)鍵字修飾:

          public class  產(chǎn)品對(duì)象 Product 的 sum {
           private int amounts;
           private int price;
           private transient int sum;
          }

          然后我們?cè)囍跏蓟?Product ,并用 GsontoJson() 方法序列化輸出 json 格式的結(jié)果。

          public static void main(String[] args) {
           Product p = new Product();
           p.setAmounts(3);
           p.setPrice(2);
           p.setSum(p.getAmounts() * p.getPrice());
           String json = new Gson().toJson(p);
           System.out.println(json);
          }

          這時(shí)控制臺(tái)是沒有打印出 sum 字段的。

          {"amounts":3,"price":2}

          我們看到,sum 屬性被 transient 修飾后,是不會(huì)被 Gson 序列化輸出的,這里就引出了使用 transient 關(guān)鍵字一個(gè)很重要的概念:對(duì)象屬性推導(dǎo)

          對(duì)象屬性推導(dǎo)

          如果一個(gè)對(duì)象的屬性值可以通過其他屬性或者方法推理出來的,那么該屬性就沒必要被序列化了。

          借此我們以 Gson 來分析被 transient 修飾過的屬性不能被序列化過程。

          首先,調(diào)用 GsontoJson() 方法,傳入 Product 對(duì)象。

          new Gson().toJson(product)

          根據(jù)傳入的產(chǎn)品對(duì)象,獲取 Product 對(duì)象的 class 類型:typeOfSrc,最后 找到對(duì)應(yīng)的對(duì)象解析適配器工廠。

          toJson(Object src, Type typeOfSrc, JsonWriter writer)
          TypeAdapter<?> adapter = getAdapter(TypeToken.get(typeOfSrc));
          for (TypeAdapterFactory factory : factories) {
           // 得到ReflectiveTypeAdapterFactory
           TypeAdapter<T> candidate = factory.create(this, type);
          }

          通過適配器 ReflectiveTypeAdapterFactory 工廠的 create() 方法,我們找到 getBoundFields 方法。

          new Adapter<T>(constructor, getBoundFields(gson, type, raw));
          for (Field field : fields) {
           boolean serialize = excludeField(field, true);
           boolean deserialize = excludeField(field, false);
           ...
          }

          這個(gè)方法做了兩件事情:

          1. 剔除被 transient 關(guān)鍵字修飾過的屬性
          2. 篩選出可序列化的屬性

          通過 excludeField() 方法,剔除被 transient 修飾過的屬性,其規(guī)則是通過位運(yùn)算 "&" 判斷 modifiers 屬性與對(duì)象屬性的 field.getModifiers() 的值是否一致,來證明該屬性是否被 transient 修飾過,如果是為真,表示剔除該屬性,不進(jìn)行序列化。

          public boolean excludeField(Field field, boolean serialize) {
           // 通過 if 判斷 modifiers 屬性
           // private int modifiers = Modifier.TRANSIENT | Modifier.STATIC;
           if ((modifiers & field.getModifiers()) != 0) {
             return true;
           } 
          }

          另外根據(jù) modifiers 屬性定義 Modifier.TRANSIENT | Modifier.STATIC 兩種類型,一種是 tranient,另一種是 static 靜態(tài)類型。

          Modifier.STATIC:靜態(tài)類型

          由約定三、我們知道,靜態(tài)變量不會(huì)被序列化。

          代碼 debug 到此,我們已經(jīng)知道 Gson 是如何證明對(duì)象是否存在被 transient 修飾過屬性以及如何過濾掉的完整過程。

          被 transient 關(guān)鍵字修飾過得變量真的不能被序列化嘛?

          想要解決這個(gè)問題,首先還要再重提一下對(duì)象的序列化方式,Java 序列化提供兩種方式。

          一種是實(shí)現(xiàn) Serializable 接口,另一種是實(shí)現(xiàn) Exteranlizable 接口。

          實(shí)現(xiàn) Exteranlizable 接口需要重寫 writeExternalreadExternal 方法,它的效率比 Serializable 高一些,并且可以決定哪些屬性需要序列化(即使是 transient 修飾的),但是對(duì)大量對(duì)象,或者重復(fù)對(duì)象,則效率低。

          從上面的這兩種序列化方式,我想你已經(jīng)看到了,使用 Exteranlizable 接口實(shí)現(xiàn)序列化時(shí),我們自己指定那些屬性是需要序列化的,即使是 transient 修飾的。下面就驗(yàn)證一下

          首先我們定義 User1 類:這個(gè)類是被 Externalizable 接口修飾的

          然后我們就可以測(cè)試了

          上面,代碼分了兩個(gè)方法,一個(gè)是序列化,一個(gè)是反序列化。里面的代碼和一開始給出的差不多,只不過,User1 里面少了 age 這個(gè)屬性。

          然后看一下結(jié)果:

          結(jié)果基本上驗(yàn)證了我們的猜想,也就是說,實(shí)現(xiàn)了 Externalizable 接口,哪一個(gè)屬性被序列化是我們手動(dòng)去指定的,即使是 transient 關(guān)鍵字修飾也不起作用。

          transient 關(guān)鍵字總結(jié)

          • 通過常用的 Gson 方式來驗(yàn)證 tranient 關(guān)鍵字不能序列化的使用場(chǎng)景。

          • 通過實(shí)現(xiàn)了 Externalizable 接口,如果手動(dòng)去指定屬性序列化的,即使是 transient 關(guān)鍵字修飾也不起作用。

          • 另外,還可以通過 javaio 包下的 ObjectInputStreamObjectOutputStream 兩個(gè)對(duì)象輸入輸出流也可以驗(yàn)證,這里就不再做贅述,感興趣的朋友可以在網(wǎng)上找找例子。

          參考

          • https://www.cnblogs.com/chenpt/p/9415249.html
          • https://blog.csdn.net/u012723673/article/details/80699029
          • https://baijiahao.baidu.com/s?id=1636557218432721275&wfr=spider&for=pc



          作者簡(jiǎn)介:編筐少年,一枚簡(jiǎn)單的北漂程序員。喜歡用簡(jiǎn)單的文字記錄工作與生活中的點(diǎn)點(diǎn)滴滴,愿與你一起分享程序員靈魂深處真正的內(nèi)心獨(dú)白。我的微信號(hào):WooolaDunzung,公眾號(hào)【猿芯輸入 1024 ,有份驚喜送給你哦。

          < END >

          推薦閱讀:

          Linux 文件搜索神器 find 實(shí)戰(zhàn)詳解,建議收藏!

          貓撲,涼了!

          搞清楚這 10 幾個(gè)后端面試問題,工作穩(wěn)了!


          關(guān)號(hào)互聯(lián)網(wǎng)全棧架構(gòu),價(jià)。

             

          瀏覽 61
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产色情AAA级AAA电影 | 免费一级做a爱片毛片A片小说 | 黄色成人免费看 | 大香蕉国产在线看 | 91丨PORNY丨成人蝌蚪 |