本篇文章給大家分享的是有關(guān)Java中值對(duì)象的作用是什么,小編覺(jué)得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話(huà)不多說(shuō),跟著小編一起來(lái)看看吧。
值對(duì)象與基礎(chǔ)類(lèi)型數(shù)據(jù)相比,富含業(yè)務(wù)語(yǔ)義,在任何使用到它的地方,其含義一看便知。它還可以封裝跟數(shù)據(jù)相關(guān)的業(yè)務(wù)邏輯,避免為了復(fù)用代碼而創(chuàng)建 util 類(lèi),更符合面向?qū)ο蟮乃枷搿?/section>? 可比較、可以被集合類(lèi)使用
相信這一點(diǎn)不需要再說(shuō)明了。那么,如何在我們的代碼中創(chuàng)建不可變對(duì)象呢?我們分為部分內(nèi)容來(lái)講,第一部分是指導(dǎo)思想,第二部分是如何進(jìn)行實(shí)踐。? 值對(duì)象創(chuàng)建指南
在 《Effective Java 第三版》 第 17 條 最小化可變性一節(jié)中,將不可變類(lèi)的設(shè)計(jì)歸納為五條原則:- 確保對(duì)任何可變組件的互斥訪(fǎng)問(wèn)
第 2、3、4 點(diǎn)很容易理解。對(duì)第 1 點(diǎn),也就是說(shuō)對(duì)任何涉及狀態(tài)變更的操作,都不能直接修改原始對(duì)象的狀態(tài),而是通過(guò)創(chuàng)建對(duì)象的副本,比如下面對(duì)復(fù)數(shù)對(duì)象的“加”操作:對(duì)于第 2 點(diǎn),確保類(lèi)不能被繼承,除了將類(lèi)設(shè)為 final,還有一種方式是將構(gòu)造方法設(shè)為 private,并向外提供靜態(tài)工廠(chǎng)方法來(lái)創(chuàng)建實(shí)例。而第 5 點(diǎn)的意思是,“如果你的類(lèi)有任何引用可變對(duì)象的屬性,請(qǐng)確保該類(lèi)的客戶(hù)端無(wú)法獲得 對(duì)這些對(duì)象的引用”。舉例而言,下面的 Period 類(lèi),盡管滿(mǎn)足上面的 1~4 點(diǎn),但由于其狀態(tài)變量中包含了引用對(duì)象,引用對(duì)象通過(guò)構(gòu)造方法與訪(fǎng)問(wèn)方法與外界共享,導(dǎo)致它的狀態(tài)也會(huì)發(fā)生變化(第 7 行、第 10 行):一個(gè)解決方案是,不使用 Date 對(duì)象,而是使用 Java 8 中提供的 LocalDate 對(duì)象,該對(duì)象是不可變的。另一種方案,在引用共享的位置對(duì)對(duì)象進(jìn)行拷貝。進(jìn)行防御性拷貝應(yīng)在參數(shù)檢查之前執(zhí)行,以避免參數(shù)檢查可拷貝期間受其它線(xiàn)程對(duì)參數(shù)更改的影響。
必要時(shí),對(duì)實(shí)現(xiàn) serializable 接口的類(lèi)進(jìn)行反序列化重寫(xiě) readObject 方法,以避免字節(jié)碼攻擊。對(duì)于這一點(diǎn),簡(jiǎn)單來(lái)講就是由于 Java 對(duì)象的反序列默認(rèn)通過(guò) readObject 方法重建對(duì)象,而不會(huì)調(diào)用我們提供的構(gòu)造方法,這使得攻擊者可以通過(guò)修改字節(jié)碼數(shù)據(jù),從而繞開(kāi)構(gòu)造方法中的參數(shù)校驗(yàn)的防御性拷貝。具體可以看 《Effective Java 第三版》 第 88 條 保護(hù)性的編寫(xiě) readObject 方法。
當(dāng)構(gòu)造方法參數(shù)過(guò)多時(shí),可以借助 builder 設(shè)計(jì)模式
這一點(diǎn)可參照《Effective Java 第三版》 第 2 條。這里不展開(kāi)了。由于不變對(duì)象在修改數(shù)據(jù)時(shí)會(huì)進(jìn)行拷貝,因此它的一個(gè)主要問(wèn)題就是可能會(huì)創(chuàng)建過(guò)多的對(duì)象,這會(huì)帶來(lái)性能問(wèn)題。一個(gè)方案是,對(duì)可能會(huì)經(jīng)常用到的對(duì)象提供公共的靜態(tài) final 常量。這一點(diǎn),既可以通過(guò)公共的常量字段來(lái)實(shí)現(xiàn),也可以通過(guò)靜態(tài)工廠(chǎng)方法來(lái)實(shí)現(xiàn)。需要重寫(xiě) equals() 和 hashCode() 方法。至于為什么以及如何實(shí)現(xiàn),相信大家都知道了,就不展開(kāi)講了。這一點(diǎn)也很好理解,既然值對(duì)象是不可變的,那么創(chuàng)建完成之后沒(méi)有任何方法可以改變的狀態(tài),因此必須在構(gòu)造時(shí)進(jìn)行必要的合法性校驗(yàn),使創(chuàng)建出來(lái)的對(duì)象滿(mǎn)足其所有的不變性條件(Invariants)。? 如何實(shí)現(xiàn)
有了指導(dǎo)思想,如何實(shí)現(xiàn)其實(shí)就一目了然了。只不過(guò),要實(shí)現(xiàn)不可變對(duì)象,需要?jiǎng)?chuàng)建大量的樣板代碼,比如 equals() 和 hashCode() 方法的重寫(xiě)、builder 模式的創(chuàng)建等等。這些重復(fù)代碼不僅寫(xiě)起來(lái)費(fèi)力,而且會(huì)使類(lèi)的核心業(yè)務(wù)邏輯隱藏在大量的樣板代碼中,降低了類(lèi)的可讀性。因此,最好實(shí)現(xiàn)方式還是借且代碼生成工具。(i) lombok @value 注解
lombok 庫(kù)的 @value 注解可以很方便的幫我們生成一個(gè)不可變的值對(duì)象類(lèi)型。如:如果我們使用 Intellij IDEA 工具,并且安裝了 lombok 插件,可以在源代碼處 右鍵 -> Refactor -> Delombok -> All lombok annotations,來(lái)查看 lombok 注解處理器處理過(guò)后生成的字節(jié)碼對(duì)應(yīng)的源代碼大概是什么樣子。這里有一點(diǎn)需要注意,lombok 工具對(duì)于引用類(lèi)型不會(huì)幫我們做防御性拷貝,因此假如我們的構(gòu)成組件包含可變對(duì)象,需要我們自己去做防御性拷貝。做法很簡(jiǎn)單,只要提供我們自己的構(gòu)造方法和 get 方法,lombok 就不會(huì)再幫我們生成對(duì)應(yīng)的方法。如果我們要對(duì)參數(shù)進(jìn)行合法性校驗(yàn),也同樣需要提供自定義的構(gòu)造方法,在構(gòu)造方法中添加校驗(yàn)邏輯。(ii) lombok @Builder 注解
lombok 的 @Builder 注解非常強(qiáng)大,可以應(yīng)用在類(lèi)上、構(gòu)造方法上,也可以應(yīng)用在靜態(tài)工廠(chǎng)方法上。在構(gòu)建時(shí)未傳入的參數(shù)為該類(lèi)型的默認(rèn)值。同樣的,如果你需要校驗(yàn),可提供自定義的全參數(shù)構(gòu)造方法。上面我們提到過(guò),對(duì)值對(duì)象的實(shí)例盡可能的重用。如果我們使用靜態(tài)工廠(chǎng)方法,就可以實(shí)現(xiàn)這一點(diǎn):注意我們把 @Builder 注解放在了
of()
靜態(tài)工廠(chǎng)方法上面,同時(shí)將構(gòu)造方法設(shè)為 private。通過(guò)查看生成的代碼,發(fā)現(xiàn) builder 的
build()
方法直接調(diào)用了該工廠(chǎng)方法。(iii) lombok @With 注解
@Value 注解會(huì)將生成的類(lèi)設(shè)為不可變,如果我們需要修改對(duì)象的狀態(tài),怎么辦?上面說(shuō)過(guò),修改狀態(tài)需要?jiǎng)?chuàng)建拷貝。使用 @With 注解可以很方便的做到這一點(diǎn)。(iv) 與 mapstruct 配合使用
在進(jìn)行領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)時(shí),我們經(jīng)常會(huì)在不同的層或者模塊之間使用不同的對(duì)象,比如持久化層使用跟數(shù)據(jù)庫(kù)紀(jì)錄進(jìn)行映射的 DO 對(duì)象,而在領(lǐng)域?qū)邮褂酶哂袠I(yè)務(wù)意義的領(lǐng)域?qū)ο?。如何在?duì)象之間進(jìn)行屬性的拷貝呢?可以有很多種選擇,我最常用的是 mapstruct 工具,該工具非常強(qiáng)大,不僅支持不同名稱(chēng)、不同類(lèi)型字段的映射,還可以使用表達(dá)式、方法調(diào)用等。對(duì)于它我們不做過(guò)多介紹,有興趣可以看
這里[11]
。在進(jìn)行屬性拷貝時(shí),通?;跓o(wú)參構(gòu)造函數(shù)創(chuàng)建對(duì)象,然后設(shè)置對(duì)應(yīng)屬性。但是上面的類(lèi),我們?cè)趯?shí)現(xiàn)不可變特性時(shí),不再提供無(wú)參構(gòu)造函數(shù)。如何讓 mapstruct 支持這種類(lèi)呢?恭喜你,只要加了 @Builder 注解,什么都不需要做,mapstruct 已經(jīng)內(nèi)置提供了對(duì) lombok @Builder 注解的支持。至于使用其它手段的屬性拷貝,我暫時(shí)沒(méi)有去了解,熟悉的同學(xué)可以參與討論。(v) json 反序列化
我們知道,當(dāng)使用 json 反序列化工具生成自定義類(lèi)型的實(shí)例時(shí),通常也是使用該類(lèi)型的默認(rèn)無(wú)參構(gòu)造方法。假如沒(méi)有該構(gòu)造方法,運(yùn)行時(shí)就會(huì)拋出異常。但是,我們不希望提供該構(gòu)造方法來(lái)破壞對(duì)象的不可變性。怎么辦呢?這里又要祭出 lombok 的另一法寶,@Jacksonized 注解。加上這一注解后,我們的不可變對(duì)象就可以被 jackson json 庫(kù)順利的創(chuàng)建出來(lái)了(需要跟 @Builder 一起使用)。其實(shí)這個(gè)注解沒(méi)什么復(fù)雜之處,能實(shí)現(xiàn)這點(diǎn)得益于 jackson json 庫(kù)本身對(duì) builder 模式的支持,@Jacksonized 注解只是按照 jackson json 的相關(guān)要求生成相關(guān)的 builder 類(lèi)和方法而已。目前 fastjson 庫(kù)似乎不支持使用 builder 模式來(lái)創(chuàng)建對(duì)象,不知道后面有沒(méi)有相關(guān)的計(jì)劃。以上就是Java中值對(duì)象的作用是什么,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見(jiàn)到或用到的。希望你能通過(guò)這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。