可变对象 不可变对象区别_对象应该是不可变的
可變對(duì)象 不可變對(duì)象區(qū)別
在面向?qū)ο蟮木幊讨?#xff0c;如果對(duì)象的狀態(tài)在創(chuàng)建后無(wú)法修改,則它是不可變的 。
在Java中,不可變對(duì)象的一個(gè)??很好的例子是String 。 創(chuàng)建后,我們無(wú)法修改其狀態(tài)。 我們可以要求它創(chuàng)建新的字符串,但是它自己的狀態(tài)永遠(yuǎn)不會(huì)改變。
但是,JDK中沒(méi)有那么多不可變的類。 以類Date為例。 可以使用setTime()修改其狀態(tài)。
我不知道為什么JDK設(shè)計(jì)師決定以不同的方式來(lái)制作這兩個(gè)非常相似的類。 但是,我認(rèn)為可變Date的設(shè)計(jì)有許多缺陷,而不變的String則更多地體現(xiàn)了面向?qū)ο蠓独木瘛?
而且,我認(rèn)為在一個(gè)完美的面向?qū)ο蟮氖澜?/strong>中所有類都應(yīng)該是不變的 。 不幸的是,有時(shí)由于JVM的限制在技術(shù)上是不可能的。 盡管如此,我們應(yīng)該始終追求最佳。
這是支持不變性的參數(shù)的不完整列表:
- 不變的對(duì)象更易于構(gòu)造,測(cè)試和使用
- 真正不可變的對(duì)象始終是線程安全的
- 它們有助于避免時(shí)間耦合
- 它們的使用無(wú)副作用(無(wú)防御性副本)
- 避免了身份變異性問(wèn)題
- 他們總是有失敗原子性
- 它們更容易緩存
- 他們防止NULL引用, 這是不好的
讓我們一一討論最重要的論點(diǎn)。
線程安全
第一個(gè)也是最明顯的論點(diǎn)是,不可變對(duì)象是線程安全的。 這意味著多個(gè)線程可以同時(shí)訪問(wèn)同一對(duì)象,而不會(huì)與另一個(gè)線程發(fā)生沖突。
如果沒(méi)有對(duì)象方法可以修改其狀態(tài),那么無(wú)論它們有多少個(gè)以及被調(diào)用的頻率是多少,它們都將在自己的堆棧內(nèi)存空間中工作。
Goetz等。 在非常著名的《 Java Concurrency in Practice》 (強(qiáng)烈推薦)一書中,更詳細(xì)地介紹了不可變對(duì)象的優(yōu)點(diǎn)。
避免時(shí)間耦合
這是時(shí)間耦合的示例(代碼發(fā)出兩個(gè)連續(xù)的HTTP POST請(qǐng)求,其中第二個(gè)包含HTTP正文):
Request request = new Request("http://example.com"); request.method("POST"); String first = request.fetch(); request.body("text=hello"); String second = request.fetch();此代碼有效。 但是,您必須記住,在配置第二個(gè)請(qǐng)求之前,應(yīng)該先配置第一個(gè)請(qǐng)求。 如果我們決定從腳本中刪除第一個(gè)請(qǐng)求,則將刪除第二行和第三行,并且不會(huì)從編譯器中得到任何錯(cuò)誤:
Request request = new Request("http://example.com"); // request.method("POST"); // String first = request.fetch(); request.body("text=hello"); String second = request.fetch();現(xiàn)在,該腳本已損壞,盡管它編譯時(shí)沒(méi)有錯(cuò)誤。 這就是時(shí)間耦合的意義所在—代碼中總是有一些程序員必須記住的隱藏信息。 在此示例中,我們必須記住,第一個(gè)請(qǐng)求的配置也用于第二個(gè)請(qǐng)求。
我們必須記住,第二個(gè)請(qǐng)求應(yīng)始終保持在一起,并在第一個(gè)請(qǐng)求之后執(zhí)行。
如果Request類是不可變的,則第一個(gè)代碼片段將一開(kāi)始就無(wú)法使用,并且將被重寫為:
final Request request = new Request(""); String first = request.method("POST").fetch(); String second = request.method("POST").body("text=hello").fetch();現(xiàn)在,這兩個(gè)請(qǐng)求沒(méi)有耦合。 我們可以安全地刪除第一個(gè),第二個(gè)仍然可以正常工作。 您可能會(huì)指出存在代碼重復(fù)。 是的,我們應(yīng)該擺脫它,然后重新編寫代碼:
final Request request = new Request(""); final Request post = request.method("POST"); String first = post.fetch(); String second = post.body("text=hello").fetch();看,重構(gòu)沒(méi)有破壞任何東西,而且我們?nèi)匀粵](méi)有時(shí)間耦合。 可以安全地從代碼中刪除第一個(gè)請(qǐng)求,而不會(huì)影響第二個(gè)請(qǐng)求。
我希望這個(gè)例子能證明操作不可變對(duì)象的代碼更具可讀性和可維護(hù)性,因?yàn)樗鼪](méi)有時(shí)間耦合。
避免副作用
讓我們嘗試在新方法中使用我們的Request類(現(xiàn)在它是可變的):
public String post(Request request) {request.method("POST");return request.fetch(); }讓我們嘗試發(fā)出兩個(gè)請(qǐng)求-第一個(gè)請(qǐng)求使用GET方法,第二個(gè)請(qǐng)求使用POST:
Request request = new Request("http://example.com"); request.method("GET"); String first = this.post(request); String second = request.fetch();方法post()具有“副作用”,它對(duì)可變對(duì)象request進(jìn)行了更改。 在這種情況下,這些更改并不是真正預(yù)期的。 我們希望它發(fā)出POST請(qǐng)求并返回其主體。 我們不想閱讀其文檔只是為了發(fā)現(xiàn)它還在后臺(tái)修改了我們作為參數(shù)傳遞給它的請(qǐng)求。
不用說(shuō),這種副作用會(huì)導(dǎo)致錯(cuò)誤和可維護(hù)性問(wèn)題。 使用不可變的Request會(huì)更好:
public String post(Request request) {return request.method("POST").fetch(); }在這種情況下,我們可能沒(méi)有任何副作用。 任何人都不能修改我們的request對(duì)象,無(wú)論它在何處使用以及方法調(diào)用傳遞給調(diào)用堆棧的深度如何:
Request request = new Request("http://example.com").method("GET"); String first = this.post(request); String second = request.fetch();此代碼是絕對(duì)安全且無(wú)副作用的。
避免身份變異
通常,如果對(duì)象的內(nèi)部狀態(tài)相同,我們希望它們相同。 Date類是一個(gè)很好的例子:
Date first = new Date(1L); Date second = new Date(1L); assert first.equals(second); // true有兩個(gè)不同的對(duì)象。 但是,它們彼此相等,因?yàn)樗鼈兊姆庋b狀態(tài)相同。 通過(guò)自定義的equals()和hashCode()方法的重載實(shí)現(xiàn),可以實(shí)現(xiàn)這一點(diǎn)。
這種方便的方法與可變對(duì)象一起使用的結(jié)果是,每次我們修改對(duì)象的狀態(tài)時(shí),它都會(huì)更改其身份:
Date first = new Date(1L); Date second = new Date(1L); first.setTime(2L); assert first.equals(second); // false在您開(kāi)始將可變對(duì)象用作地圖中的鍵之前,這看起來(lái)很自然:
Map<Date, String> map = new HashMap<>(); Date date = new Date(); map.put(date, "hello, world!"); date.setTime(12345L); assert map.containsKey(date); // false修改date狀態(tài)對(duì)象時(shí),我們不希望它更改其身份。 我們不希望僅因?yàn)槠滏I的狀態(tài)已更改而在映射中丟失條目。 但是,這正是以上示例中發(fā)生的情況。
當(dāng)我們向地圖添加對(duì)象時(shí),其hashCode()返回一個(gè)值。 HashMap使用此值將條目放置到內(nèi)部哈希表中。 當(dāng)我們調(diào)用containsKey()時(shí),對(duì)象的哈希碼是不同的(因?yàn)樗谄鋬?nèi)部狀態(tài)),并且HashMap在內(nèi)部哈希表中找不到它。
調(diào)試可變對(duì)象的副作用非常煩人且困難。 不可變的對(duì)象完全避免了它。
失效原子性
這是一個(gè)簡(jiǎn)單的示例:
public class Stack {private int size;private String[] items;public void push(String item) {size++;if (size > items.length) {throw new RuntimeException("stack overflow");}items[size] = item;} }顯然,如果Stack類在溢出時(shí)引發(fā)運(yùn)行時(shí)異常,則該對(duì)象將處于斷開(kāi)狀態(tài)。 它的size屬性將增加,而items將不會(huì)獲得新元素。
不變性可以防止此問(wèn)題。 對(duì)象永遠(yuǎn)不會(huì)處于損壞狀態(tài),因?yàn)樗臓顟B(tài)僅在其構(gòu)造函數(shù)中被修改。 構(gòu)造函數(shù)將失敗,拒絕對(duì)象實(shí)例化,或者成功,則將生成有效的固態(tài)對(duì)象,該對(duì)象永遠(yuǎn)不會(huì)更改其封裝狀態(tài)。
有關(guān)此主題的更多信息,請(qǐng)閱讀Joshua Bloch撰寫的有效Java,第二版 。
反對(duì)不變性的爭(zhēng)論
有許多反對(duì)不變性的論點(diǎn)。
如果您還有其他論點(diǎn),請(qǐng)?jiān)谙旅姘l(fā)表,我將嘗試發(fā)表評(píng)論。
翻譯自: https://www.javacodegeeks.com/2014/09/objects-should-be-immutable.html
可變對(duì)象 不可變對(duì)象區(qū)別
總結(jié)
以上是生活随笔為你收集整理的可变对象 不可变对象区别_对象应该是不可变的的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java中的记录类型
- 下一篇: 使用SoapUI调用不同的安全WCF S