日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

对象应该是不可变的

發(fā)布時間:2023/12/3 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 对象应该是不可变的 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在面向對象的編程中,如果對象的狀態(tài)在創(chuàng)建后無法修改,則該對象是不可變的 。

在Java中,不可變對象的一個??很好的例子是String 。 創(chuàng)建完成后,我們將無法修改其狀態(tài)。 我們可以要求它創(chuàng)建新的字符串,但是它自己的狀態(tài)永遠不會改變。

但是,JDK中沒有那么多不可變的類。 以類Date為例。 可以使用setTime()修改其狀態(tài)。

我不知道為什么JDK設計師決定以不同的方式來制作這兩個非常相似的類。 但是,我認為可變Date的設計有許多缺陷,而不變的String則更多地體現了面向對象范例的精神。

而且,我認為在一個完美的面向對象的世界所有類都應該是不變的 。 不幸的是,有時由于JVM的限制在技術上是不可能的。 盡管如此,我們應該始終追求最佳。

這是支持不變性的參數的不完整列表:

  • 不變的對象更易于構造,測試和使用
  • 真正不可變的對象始終是線程安全的
  • 它們有助于避免時間耦合
  • 它們的使用無副作用(無防御性副本)
  • 避免身份變異性問題
  • 他們總是有失敗原子性
  • 它們更容易緩存
  • 他們防止NULL引用, 這是不好的

讓我們一一討論最重要的論點。

線程安全

第一個也是最明顯的論點是,不可變對象是線程安全的。 這意味著多個線程可以同時訪問同一對象,而不會與另一個線程發(fā)生沖突。

如果沒有對象方法可以修改其狀態(tài),那么無論它們有多少個以及被調用的頻率是多少,它們都將在自己的堆棧內存空間中工作。

Goetz等。 在非常著名的《 Java Concurrency in Practice》 (強烈推薦)一書中,更詳細地介紹了不可變對象的優(yōu)點。

避免時間耦合

這是時間耦合的示例(代碼發(fā)出兩個連續(xù)的HTTP POST請求,其中第二個包含HTTP正文):

Request request = new Request("http://example.com"); request.method("POST"); String first = request.fetch(); request.body("text=hello"); String second = request.fetch();

此代碼有效。 但是,您必須記住,應該在配置第二個請求之前配置第一個請求。 如果我們決定從腳本中刪除第一個請求,則將刪除第二行和第三行,并且不會從編譯器中得到任何錯誤:

Request request = new Request("http://example.com"); // request.method("POST"); // String first = request.fetch(); request.body("text=hello"); String second = request.fetch();

現在,該腳本已損壞,盡管它編譯時沒有錯誤。 這就是時間耦合的意義所在—代碼中總是有一些程序員必須記住的隱藏信息。 在此示例中,我們必須記住,第一個請求的配置也用于第二個請求。

我們必須記住,第二個請求應始終保持在一起,并在第一個請求之后執(zhí)行。

如果Request類是不可變的,則第一個代碼段將一開始就無法工作,并且將被重寫為:

final Request request = new Request(""); String first = request.method("POST").fetch(); String second = request.method("POST").body("text=hello").fetch();

現在,這兩個請求沒有耦合。 我們可以安全地刪除第一個,第二個仍然可以正常工作。 您可能會指出存在代碼重復。 是的,我們應該擺脫它,然后重新編寫代碼:

final Request request = new Request(""); final Request post = request.method("POST"); String first = post.fetch(); String second = post.body("text=hello").fetch();

瞧,重構并沒有破壞任何東西,我們仍然沒有時間耦合。 可以安全地從代碼中刪除第一個請求,而不會影響第二個請求。

我希望這個例子能證明操作不可變對象的代碼更具可讀性和可維護性,因為它沒有時間上的耦合。

避免副作用

讓我們嘗試在新方法中使用我們的Request類(現在它是可變的):

public String post(Request request) {request.method("POST");return request.fetch(); }

讓我們嘗試發(fā)出兩個請求-第一個請求使用GET方法,第二個請求使用POST:

Request request = new Request("http://example.com"); request.method("GET"); String first = this.post(request); String second = request.fetch();

方法post()具有“副作用”,它對可變對象request進行了更改。 在這種情況下,這些更改并不是真正預期的。 我們希望它發(fā)出POST請求并返回其主體。 我們不想閱讀其文檔只是為了發(fā)現它還在后臺修改了我們作為參數傳遞給它的請求。

不用說,這種副作用會導致錯誤和可維護性問題。 使用不可變的Request會更好:

public String post(Request request) {return request.method("POST").fetch(); }

在這種情況下,我們可能沒有任何副作用。 任何人都不能修改我們的request對象,無論它在何處使用以及方法調用傳遞給調用堆棧的深度如何:

Request request = new Request("http://example.com").method("GET"); String first = this.post(request); String second = request.fetch();

此代碼是絕對安全且無副作用的。

避免身份變異

通常,如果對象的內部狀態(tài)相同,我們希望它們相同。 Date類是一個很好的例子:

Date first = new Date(1L); Date second = new Date(1L); assert first.equals(second); // true

有兩個不同的對象。 但是,它們彼此相等,因為它們的封裝狀態(tài)相同。 通過自定義的equals()和hashCode()方法的重載實現,可以實現這一點。

這種方便的方法與可變對象一起使用的結果是,每次我們修改對象的狀態(tài)時,它都會更改其身份:

Date first = new Date(1L); Date second = new Date(1L); first.setTime(2L); assert first.equals(second); // false

在您開始將可變對象用作地圖中的鍵之前,這看起來很自然:

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)對象時,我們不希望它更改其身份。 我們不希望僅因為其鍵的狀態(tài)已更改而在映射中丟失條目。 但是,這正是上面示例中發(fā)生的情況。

當我們向地圖添加對象時,其hashCode()返回一個值。 HashMap使用此值將條目放置到內部哈希表中。 當我們調用containsKey()時,對象的哈希碼是不同的(因為它基于其內部狀態(tài)),并且HashMap在內部哈希表中找不到它。

調試可變對象的副作用非常煩人且困難。 不可變的對象完全避免了它。

失效原子性

這是一個簡單的示例:

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類在溢出時引發(fā)運行時異常,則該對象將處于斷開狀態(tài)。 它的size屬性將增加,而items將不會獲得新元素。

不變性可以防止此問題。 對象永遠不會處于損壞狀態(tài),因為僅在其構造函數中修改了對象的狀態(tài)。 構造函數將失敗,拒絕對象實例化,或者成功,則將生成有效的固態(tài)對象,該對象永遠不會更改其封裝狀態(tài)。

有關此主題的更多信息,請閱讀Joshua Bloch撰寫的有效Java,第二版 。

反對不變性的爭論

有許多反對不變性的論點。

  • “不可遷移性不適用于企業(yè)系統”。 我經常聽到人們說不變性是一種奇特的功能,而在實際企業(yè)系統中絕對不可行。 作為反駁,我只能顯示一些僅包含不可變Java對象的實際應用程序示例: jcabi-http , jcabi-xml , jcabi-github , jcabi-s3 , jcabi-dynamo , jcabi-simpledb所有僅與不可變類/對象一起使用的Java庫。 netbout.com和stateful.co是僅與不可變對象一起使用的Web應用程序。
  • “更新現有對象比創(chuàng)建新對象便宜”。 Oracle 認為 :“對象創(chuàng)建的影響通常被高估了,并且可以被與不可變對象相關聯的某些效率所抵消。 其中包括由于垃圾收集而減少的開銷,以及消除了保護可變對象免受損壞所需的代碼。” 我同意。
  • 如果您還有其他論點,請在下面發(fā)表,我將嘗試發(fā)表評論。

    翻譯自: https://www.javacodegeeks.com/2014/09/objects-should-be-immutable.html

    創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的对象应该是不可变的的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。