溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Java中值對(duì)象的作用是什么

發(fā)布時(shí)間:2021-07-01 15:39:54 來(lái)源:億速云 閱讀:598 作者:Leah 欄目:編程語(yǔ)言

本篇文章給大家分享的是有關(guān)Java中值對(duì)象的作用是什么,小編覺(jué)得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話(huà)不多說(shuō),跟著小編一起來(lái)看看吧。

值類(lèi)型與值對(duì)象


我們都知道,Java 語(yǔ)言中的類(lèi)型分為兩種:基本類(lèi)型(primitive type)和引用類(lèi)型(reference type),這不僅是語(yǔ)言層面的特性,也由 JVM 內(nèi)在實(shí)現(xiàn)支持  [1]  。

其中,基本類(lèi)型指是的 8 種基本的數(shù)值類(lèi)型:boolean、byte、char、int、short、long、float、double;而引用類(lèi)型,指的是對(duì)程序中創(chuàng)建的對(duì)象的引用,可以理解為指向?qū)ο蟮闹羔樆蚓浔?。Java 號(hào)稱(chēng)一切皆是對(duì)象,很可惜,這并不是事實(shí),基本類(lèi)型就不是對(duì)象。

那么,值類(lèi)型又是什么呢?

在你編寫(xiě)程序時(shí),是否經(jīng)常會(huì)遇到一些需要表達(dá)數(shù)值或其它類(lèi)型值的場(chǎng)景?比如復(fù)數(shù)、向量、顏色值、坐標(biāo)點(diǎn)、時(shí)間、日期等。這些值通常無(wú)法用基本類(lèi)型來(lái)表達(dá),一則它可能是多個(gè)屬性構(gòu)成,二則針對(duì)值的一些操作或邏輯我們希望跟數(shù)據(jù)封裝在一起,比如向量的點(diǎn)乘、叉乘、取模等。但如果使用對(duì)象來(lái)表達(dá)同樣也會(huì)產(chǎn)生很多問(wèn)題:

?  相等性比較
對(duì)這些對(duì)象的比較是有意義的,但是默認(rèn)情況下 Java 對(duì)象比較的是地址,因此直接比較的結(jié)果通常不是我們期待的行為:

Java中值對(duì)象的作用是什么

?  可變性

對(duì)引用類(lèi)型的賦值、方法傳參等會(huì)生成多個(gè)引用,這些引用都指向同一個(gè)對(duì)象。這在一些情況下是沒(méi)有問(wèn)題的,但在某些場(chǎng)景下可能導(dǎo)致對(duì)象發(fā)生預(yù)期之外的變化。如:

Java中值對(duì)象的作用是什么

上面的 case 比較簡(jiǎn)單,只要對(duì) Date 的特性有些了解就不會(huì)犯這樣的錯(cuò)誤。但如果對(duì)象經(jīng)過(guò)多次傳遞,使用的位置離創(chuàng)建的位置很遠(yuǎn)的話(huà),我們就未必能這么謹(jǐn)慎了。這種問(wèn)題,Martin Flower 稱(chēng)之為   aliasing bug[2]  。

?  性能

上面兩點(diǎn)其實(shí)都容易解決,只是每個(gè)實(shí)現(xiàn)需要寫(xiě)很多樣板代碼。需要比較的對(duì)象只要重寫(xiě)   equals()   和   hashCode   方法即可;對(duì)于可變性問(wèn)題,可以將對(duì)象設(shè)計(jì)為不可變對(duì)象,在修改時(shí)返回一個(gè)深拷貝副本來(lái)供客戶(hù)端操作。滿(mǎn)足上述兩種條件的對(duì)象,我們可以稱(chēng)之為值對(duì)象。

那么,通過(guò)“對(duì)象”來(lái)實(shí)現(xiàn)我們對(duì)這種數(shù)據(jù)結(jié)構(gòu)的訴求,是否是最好的方式呢?

我們知道,Java 中的對(duì)象通常是分配在堆上,通過(guò)引用來(lái)進(jìn)行操作,不過(guò)這不是必然的。JVM 有一項(xiàng)技術(shù)叫  逃逸分析[3]  ,可以在運(yùn)行時(shí)分析出一個(gè)方法中創(chuàng)建的對(duì)象是否會(huì)逃逸到方法或線(xiàn)程外部,如果沒(méi)有逃逸,可以進(jìn)而執(zhí)行一些編譯優(yōu)化,比如棧上分配、同步消除、標(biāo)量替換等。如果一個(gè)對(duì)象被分配到棧上,就意味著當(dāng)方法結(jié)束后就會(huì)自動(dòng)銷(xiāo)毀,省去了 GC 的開(kāi)銷(xiāo),這對(duì)于優(yōu)化應(yīng)用內(nèi)存占用和 GC 停頓時(shí)間來(lái)說(shuō),無(wú)疑是個(gè)好消息;而標(biāo)量替換意味著壓根就不會(huì)創(chuàng)建對(duì)象,相關(guān)數(shù)據(jù)被替換成基本類(lèi)型數(shù)據(jù)直接分配到棧上,不僅省去了對(duì)象操作相關(guān)開(kāi)銷(xiāo),也更利于 CPU 高速緩存或寄存器進(jìn)行優(yōu)化。

對(duì)于值對(duì)象來(lái)說(shuō),一般極少有共享的需求,假如能直接在棧上進(jìn)行分配,那么將省去對(duì)象的存儲(chǔ)、訪(fǎng)問(wèn)和 GC 的成本,對(duì)程序性能非常有利。不過(guò)進(jìn)行逃逸分析也是有成本的,如果在語(yǔ)言層面直接支持的話(huà),就可以進(jìn)一步減少編譯時(shí)分析的開(kāi)銷(xiāo)。不過(guò),目前 Java 語(yǔ)言還做不到這一點(diǎn)。

當(dāng)一門(mén)編程語(yǔ)言為上述類(lèi)型的數(shù)據(jù)結(jié)構(gòu)提供內(nèi)在支持時(shí),該類(lèi)型可稱(chēng)之為值類(lèi)型。而對(duì)于滿(mǎn)足上述訴求的實(shí)例,無(wú)論是基于值類(lèi)型實(shí)現(xiàn)還是普通對(duì)象類(lèi)型實(shí)現(xiàn),我們都可以稱(chēng)之為值對(duì)象。

不同編程語(yǔ)言對(duì)值類(lèi)型的支持

?  Java
上面已經(jīng)說(shuō)過(guò),Java 語(yǔ)言層面原生并不支持值類(lèi)型。不過(guò),它提供了許多具有值類(lèi)型特點(diǎn)的類(lèi),比如:8個(gè)基本類(lèi)型對(duì)應(yīng)的封裝類(lèi)、String、BigDecimal 等,這些類(lèi)的共同特點(diǎn)之一就是不可變性,同時(shí)也都對(duì)比較操作做了實(shí)現(xiàn),因此都可看作值對(duì)象。另外一個(gè)應(yīng)該設(shè)計(jì)為不可變、但實(shí)際可變的類(lèi)是 java.util.Date 類(lèi),也因?yàn)槿绱?,Date 類(lèi)飽受詬病。在 Java 8 中官方正式推出新的 時(shí)間/日期 API,試圖取代 Date 相關(guān)接口,這些新的類(lèi)全部被設(shè)計(jì)成了不可變類(lèi)。

對(duì)于Java 是否應(yīng)該從語(yǔ)言層面支持值類(lèi)型的討論由來(lái)已久,比如這篇  JEP提案[4]  早在 2012 時(shí)就提議支持值對(duì)象;oracle 論壇上的這篇  博客[5]  也對(duì)如何實(shí)現(xiàn)值對(duì)象做了探討。最近有兩篇提案,一個(gè)提出了   Primitive Object  [6]  的概念,可算是值類(lèi)型的一種實(shí)現(xiàn);另外一篇提議  基于Primitive Object統(tǒng)一基本類(lèi)型與對(duì)象類(lèi)型[7]  。不過(guò),這兩個(gè)提案仍處于   Submitted   階段(JEP 提案從提出到發(fā)布的流程有幾個(gè)階段,可以看     這里  [8]   Process states 一節(jié)),能否被采納、實(shí)現(xiàn)乃至發(fā)布到正式版本,還是未知之?dāng)?shù)。

?  C++

C++ 中沒(méi)有值對(duì)象這一概念,不過(guò)在創(chuàng)建對(duì)象時(shí),允許開(kāi)發(fā)者選擇在堆上還是在棧上創(chuàng)建。比如下面的示例代碼,直接通過(guò)   A a;   的方式創(chuàng)建的對(duì)象是分配在棧上的,而通過(guò)   new A();   的方式創(chuàng)建的對(duì)象分配在堆上,并且返回一個(gè)指向該對(duì)象的指針。在棧上創(chuàng)建的對(duì)象在函數(shù)執(zhí)行結(jié)束時(shí)會(huì)自動(dòng)銷(xiāo)毀。

更進(jìn)一步,對(duì) A 類(lèi)型的對(duì)象進(jìn)行賦值(34行)或方法傳參(38行)時(shí),會(huì)產(chǎn)生一次拷貝操作,生成一個(gè)新的對(duì)象,新對(duì)象的作用域分別為當(dāng)前函數(shù)和被調(diào)函數(shù),相應(yīng)函數(shù)執(zhí)行結(jié)束時(shí)也會(huì)被銷(xiāo)毀。而對(duì)指針類(lèi)型的對(duì)象進(jìn)行賦值(43行)和方法傳參(45行)時(shí),盡管創(chuàng)建了新的指針對(duì)象,新的指針仍然指向相同的對(duì)象。

可見(jiàn) C++ 中對(duì)類(lèi)類(lèi)型和指針類(lèi)型的使用,分別具有值類(lèi)型和引用類(lèi)型的一些特點(diǎn)。

Java中值對(duì)象的作用是什么

?  C#
C# 語(yǔ)言中是明確的提出了  值類(lèi)型[9]  這一概念的,struct 就是一種值類(lèi)型。MSDN文檔中說(shuō)明:“默認(rèn)情況下,在分配中,通過(guò)將實(shí)參傳遞給方法并返回方法結(jié)果來(lái)復(fù)制變量值?!?在賦值操作時(shí),也同樣會(huì)對(duì)對(duì)象進(jìn)行拷貝。如下面的代碼所示,我們可以看到將 p1 賦值給 p2,p2 修改狀態(tài)后,p1 中的數(shù)據(jù)仍然保持不變。

另外,在 C# 中值類(lèi)型是分配在棧上的,值類(lèi)型與引用類(lèi)型之間可以進(jìn)行轉(zhuǎn)化,稱(chēng)之為裝箱和拆箱,上面的 Java Primitive Object 提案似乎也借鑒了 C# 的設(shè)計(jì)思想。

Java中值對(duì)象的作用是什么

?  其它語(yǔ)言

其它編程語(yǔ)言對(duì)值類(lèi)型的支持不盡相同。以函數(shù)式編程為例,大多數(shù)函數(shù)式編程語(yǔ)言中變量都是不可變的,因此在函數(shù)式語(yǔ)言中定義的數(shù)據(jù)結(jié)構(gòu)都可看作是值類(lèi)型。

DDD 中的值對(duì)象

盡管 Java 并沒(méi)有對(duì)值對(duì)象提供語(yǔ)言層面的類(lèi)型支持,但這并不妨礙我們?cè)谧约旱拇a中創(chuàng)建事實(shí)上的值對(duì)象。實(shí)際上值對(duì)象[10]的定義可以并不僅限于類(lèi)似向量、顏色值、坐標(biāo)點(diǎn)這樣一些使用范圍。Martin Flower 認(rèn)為,  值對(duì)象  在編程中的作用被極大的忽視了,善于值對(duì)象可以非常有效的簡(jiǎn)化你的系統(tǒng)代碼;Vaughn Vernon 在《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》一書(shū)中甚至說(shuō),我們應(yīng)該盡量使用值對(duì)象建模而不是實(shí)體對(duì)象。實(shí)際上,當(dāng)提到“值對(duì)象”這個(gè)概念時(shí),最常見(jiàn)的就是在 DDD(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì))這個(gè)上下文中。

Eric Evans 在《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì) 軟件核心復(fù)雜性應(yīng)對(duì)之道》一書(shū)中提出了實(shí)體(Enity)與值對(duì)象(Value Object)的概念。Vaughn Vernon 在《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》中做了進(jìn)一步闡述。

在 DDD 中,實(shí)體代表具有個(gè)性特征或需要區(qū)分不同個(gè)體的對(duì)象,它具有唯一標(biāo)識(shí)和可變性。對(duì)于實(shí)體對(duì)象,我們首要考慮的并不是其屬性,而是能代表其本質(zhì)特征的唯一標(biāo)識(shí),無(wú)論對(duì)象屬性如何變化,它都是同一個(gè)對(duì)象,它的生命周期具有連續(xù)性,甚至對(duì)對(duì)象進(jìn)行持久化存儲(chǔ)然后基于存儲(chǔ)來(lái)重建對(duì)象,它仍然是同一個(gè)對(duì)象的延續(xù)。

而值對(duì)象,它通常是一些屬性的集合,是對(duì)對(duì)象的度量和描述。值對(duì)象應(yīng)該是不可變的,當(dāng)度量和描述改變時(shí),可以用另外一個(gè)值對(duì)象替換。值可以跟其它值對(duì)象進(jìn)行相等性比較。

可以看到,在 DDD 中的值對(duì)象的定義跟我們上面的描述非常相似?!秾?shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》對(duì)于值對(duì)象的闡述非常詳盡,想要進(jìn)一步了解的可以閱讀該書(shū)第 6 章內(nèi)容。

使用值對(duì)象的好處

因?yàn)橹祵?duì)象通常設(shè)計(jì)為不可變對(duì)象,因此值對(duì)象的好處首先就是不可變對(duì)象的好處。另外在支持值類(lèi)型的語(yǔ)言中,值對(duì)象的創(chuàng)建、操作、銷(xiāo)毀會(huì)有更好的性能。

?  線(xiàn)程安全
在 Java 編程語(yǔ)言中,出現(xiàn)線(xiàn)程安全問(wèn)題的必要條件有兩個(gè):對(duì)象狀態(tài)被多個(gè)線(xiàn)程共享;對(duì)象狀態(tài)可變。因此解決線(xiàn)程安全問(wèn)題的思路也主要從幾個(gè)方向出發(fā):無(wú)狀態(tài);狀態(tài)不可變;不共享狀態(tài);通過(guò)同步機(jī)制來(lái)序列化對(duì)象狀態(tài)的訪(fǎng)問(wèn)。

而不可變對(duì)象狀態(tài)是不變的,因此是線(xiàn)程安全的,可以放心應(yīng)用到并發(fā)環(huán)境中,無(wú)需額外的同步機(jī)制在多個(gè)線(xiàn)程中共享。

?  避免 Alias Bug

Aliasing bug 的概念上文已經(jīng)講過(guò),主要是指多個(gè)對(duì)象的引用被分享到多個(gè)環(huán)境中后,在某個(gè)環(huán)境的改動(dòng)會(huì)導(dǎo)致從另外一個(gè)環(huán)境中看到預(yù)期之外的變化。

最近我們的項(xiàng)目中就遇到這樣一個(gè) bug,某個(gè)對(duì)象會(huì)被緩存到本地內(nèi)存中,取出對(duì)象后,返回給 UI 層的某個(gè)屬性值需要根據(jù)請(qǐng)求環(huán)境做一些判斷與變更,由于未做防御性拷貝,導(dǎo)致變化污染了緩存對(duì)象,后面的請(qǐng)求出現(xiàn)錯(cuò)誤的結(jié)果。

而不可變對(duì)象不允許修改屬性值,任何狀態(tài)的變化必須通過(guò)創(chuàng)建副本來(lái)實(shí)現(xiàn),因此可以有效的避免該類(lèi) bug。

?  簡(jiǎn)化邏輯復(fù)雜程度
  • 任何使用到值對(duì)象的地方,它的狀態(tài)始終是合法的。通常不可變對(duì)象會(huì)在創(chuàng)建時(shí)進(jìn)行自校驗(yàn),因此一旦創(chuàng)建完成,它始終處于合法有效的狀態(tài)之中,沒(méi)有任何行為能使破壞它的一致性狀態(tài)。

  • 可以安全的共享給其它對(duì)象、其它線(xiàn)程,而不用擔(dān)心狀態(tài)發(fā)生變化,簡(jiǎn)化了代碼維護(hù)者對(duì)流程、邏輯的理解。

  • 可以作為構(gòu)件簡(jiǎn)化其它對(duì)象的狀態(tài)管理。當(dāng)其它對(duì)象使用不可變對(duì)象作為其構(gòu)件時(shí),由于不可變對(duì)象自身狀態(tài)不變,使得它在被傳入和獲取時(shí)不需要進(jìn)行防御性拷貝,簡(jiǎn)化了對(duì)象狀態(tài)的跟蹤。

     
?  使你的設(shè)計(jì)更清晰
值對(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ō)明了。

值對(duì)象 Java 實(shí)踐


那么,如何在我們的代碼中創(chuàng)建不可變對(duì)象呢?我們分為部分內(nèi)容來(lái)講,第一部分是指導(dǎo)思想,第二部分是如何進(jìn)行實(shí)踐。

?  值對(duì)象創(chuàng)建指南
  • 創(chuàng)建不可變對(duì)象
在 《Effective Java 第三版》 第 17 條 最小化可變性一節(jié)中,將不可變類(lèi)的設(shè)計(jì)歸納為五條原則:
  • 不要提供修改對(duì)象狀態(tài)的方法
  • 確保這個(gè)類(lèi)不能被繼承
  • 把所有屬性設(shè)置為 final
  • 把所有的屬性設(shè)置為 private
  • 確保對(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ì)象的“加”操作:

Java中值對(duì)象的作用是什么

對(duì)于第 2 點(diǎn),確保類(lèi)不能被繼承,除了將類(lèi)設(shè)為 final,還有一種方式是將構(gòu)造方法設(shè)為 private,并向外提供靜態(tài)工廠(chǎng)方法來(lái)創(chuàng)建實(shí)例。

Java中值對(duì)象的作用是什么

而第 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 行):

Java中值對(duì)象的作用是什么

一個(gè)解決方案是,不使用 Date 對(duì)象,而是使用 Java 8 中提供的 LocalDate 對(duì)象,該對(duì)象是不可變的。另一種方案,在引用共享的位置對(duì)對(duì)象進(jìn)行拷貝。

由此可以延伸出:
  • 盡可能使用不可變對(duì)象作為構(gòu)建對(duì)象的組件;

  • 必要時(shí)對(duì)構(gòu)造方法參數(shù)和方法返回值進(jìn)行防御性拷貝:(第 6、7、14、18 行)


Java中值對(duì)象的作用是什么

這里還要注意幾點(diǎ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)了。

  • 盡可能重用實(shí)例
由于不變對(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)講了。

  • 創(chuàng)建即合法
這一點(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)
  • 手寫(xiě)代碼
有了指導(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)型。如:

Java中值對(duì)象的作用是什么

如果我們使用 Intellij IDEA 工具,并且安裝了 lombok 插件,可以在源代碼處 右鍵 -> Refactor -> Delombok -> All lombok annotations,來(lái)查看 lombok 注解處理器處理過(guò)后生成的字節(jié)碼對(duì)應(yīng)的源代碼大概是什么樣子。

Java中值對(duì)象的作用是什么

這里有一點(diǎn)需要注意,lombok 工具對(duì)于引用類(lèi)型不會(huì)幫我們做防御性拷貝,因此假如我們的構(gòu)成組件包含可變對(duì)象,需要我們自己去做防御性拷貝。做法很簡(jiǎn)單,只要提供我們自己的構(gòu)造方法和 get 方法,lombok 就不會(huì)再幫我們生成對(duì)應(yīng)的方法。

Java中值對(duì)象的作用是什么

如果我們要對(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)造方法。

Java中值對(duì)象的作用是什么

上面我們提到過(guò),對(duì)值對(duì)象的實(shí)例盡可能的重用。如果我們使用靜態(tài)工廠(chǎng)方法,就可以實(shí)現(xiàn)這一點(diǎn):

Java中值對(duì)象的作用是什么

注意我們把 @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)。

Java中值對(duì)象的作用是什么
(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è)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI