不變性的來龍去脈

因此,在我的第一篇文章中,我談到了一些構建器模式,并提到了一個非常強大但卻被忽視的概念:不變性。

什么是不可變類? 這只是一個其實例無法修改的類。 類屬性的每個值都在其聲明或其構造函數中設置,并在對象的整個生命周期中保留這些值。 Java有很多不變的類,例如String ,所有帶框的原語( DoubleIntegerFloat等), BigIntegerBigDecimal等。 這有一個很好的理由:不可變類比可變類更容易設計,實現和使用。 一旦實例化它們,它們只能處于一種狀態,因此它們不易出錯,并且,正如我們在本文后面會看到的那樣,它們更加安全。

您如何確保類是不變的? 只需遵循以下5個簡單步驟:

  1. 不要提供任何可修改對象狀態的公共方法 ,也稱為增變器(例如setter)。
  2. 防止擴展類 。 這不允許任何惡意或粗心的類擴展我們的類并損害其不變的行為。 這樣做的通常且更簡單的方法是將類標記為final ,但是我將在本文中提及另一種方法。
  3. 將所有字段定為最終值 。 這是讓編譯器為您強制執行第1點的方法。 此外,它清楚地使任何看到您的代碼的人都知道,您不希望這些字段在設置后更改其值。
  4. 將所有字段設為私有 。 無論您是否考慮不變性,這一點都應該很明顯,并且您應該遵循它 ,但是我只是為了以防萬一。
  5. 永遠不要提供對任何可變屬性的訪問 。 如果您的類具有一個可變對象作為其屬性之一(例如ListMap或您的域問題中的任何其他可變對象),請確保該類的客戶端永遠無法獲得對該對象的引用。 這意味著您永遠不要從訪問器(例如,getter)直接返回對它們的引用,并且永遠不要在構造函數中使用從客戶端作為參數傳遞的引用來初始化它們。 在這種情況下,您應該始終制作防御性副本。

有很多理論,沒有代碼,因此讓我們看一個簡單的不可變類是什么樣子,以及它如何處理我之前提到的5個步驟:

public class Book {private final String isbn;private final int publicationYear;private final List reviews;private Book(BookBuilder builder) {this.isbn = builder.isbn;this.publicationYear = builder.publicationYear;this.reviews = Lists.newArrayList(builder.reviews);}public String getIsbn() {return isbn;}public int getPublicationYear() {return publicationYear;}public List getReviews() {return Lists.newArrayList(reviews);}public static class BookBuilder {private String isbn;private int publicationYear;private List reviews;public BookBuilder isbn(String isbn) {this.isbn = isbn;return this;}public BookBuilder publicationYear(int year) {this.publicationYear = year;return this;}public BookBuilder reviews(List reviews) {this.reviews = reviews == null ? new ArrayList() : reviews;return this;}public Book build() {return new Book(this);}}
}

我們將在這個非常簡單的課程中講解重點。 首先,您可能已經注意到,我再次使用了構建器模式。 這不僅是因為我是它的忠實擁護者,而且還因為我想說明一些我不想在之前的帖子中沒有首先對不變性概念有基本了解的觀點。 現在,讓我們看一下我提到的5個步驟,您需要遵循這些步驟使一個類不可變,并查看它們是否對本書示例有效:

    • 不要提供任何修改對象狀態的公共方法 。 請注意,該類上的唯一方法是其私有構造函數和其屬性的獲取器,但沒有更改對象狀態的方法。
    • 防止擴展類 。 這個很棘手。 我提到確保這一點的最簡單方法是將班級定為最終班,但Book班顯然不是最終班。 但是,請注意,唯一可用的構造函數是private 。 編譯器確保沒有公共或受保護的構造函數的類不能被子類化。 因此,在這種情況下,不必在類聲明中使用final關鍵字,但無論如何將其包括在內只是一個好主意,以使看到您代碼的任何人都可以清楚地知道。
    • 將所有字段定為最終值 。 非常簡單,該類上的所有屬性都聲明為final
    • 永遠不要提供對任何可變屬性的訪問 。 這實際上很有趣。 注意Book類如何具有一個List <String>屬性,該屬性被聲明為final,并且其值在類構造函數上設置。 但是,此列表是可變對象。 也就是說,雖然評論參考一旦設置就無法更改,但列表的內容可以更改。 引用相同列表的客戶端可以添加或刪除元素,結果,在創建Book對象后更改其狀態。 因此,請注意,在Book構造函數中,我們不直接分配引用。 相反,我們使用Guava庫通過調用“ this.reviews = Lists.newArrayList(builder.reviews); ”來復制列表this.reviews = Lists.newArrayList(builder.reviews); ”。 在getReviews方法上可以看到相同的情況,在該方法中,我們返回列表的副本而不是直接引用。 值得注意的是,此示例可能有點過于簡化,因為評論列表只能包含不可變的字符串。 如果列表的類型是可變的類,那么您還必須復制列表中的每個對象,而不僅僅是列表本身。

最后一點說明了為什么不可變的類導致更簡潔的設計和更易于閱讀的代碼。 您只需共享那些不可變的對象,而不必擔心防御性副本。 實際上,絕對不要制作任何副本,因為對象的任何副本都將永遠等于原始副本。 一個必然的結論是,不變的對象僅僅是簡單的。 他們只能處于一種狀態,并且一生都保持這種狀態。 您可以使用類構造函數來檢查任何不變量(即,需要在該類上有效的條件,例如其屬性之一的值范圍),然后可以確保這些不變量保持真實狀態而無需付出任何努力您或您的客戶。

不變對象的另一個巨大好處是它們本質上是線程安全的。 它們不能被同時訪問對象的多個線程破壞。 到目前為止,這是在應用程序中提供線程安全性的最簡單且不易出錯的方法。

但是,如果您已經有一個Book實例并且想要更改其屬性之一的值怎么辦? 換句話說,您想要更改對象的狀態。 在不可變的類上,根據定義,這是不可能的。 但是,就像軟件中的大多數事情一樣,總有一種解決方法。 在這種情況下,實際上有兩個。

第一種選擇是在Book類上使用Fluent Interface技術,并具有類似于setter的方法,這些方法實際上創建一個對象,該對象的所有屬性都具有相同的值,但要更改的對象除外。 在我們的示例中,我們將必須在Book類中添加以下內容:

private Book(BookBuilder builder) {this(builder.isbn, builder.publicationYear, builder.reviews);}private Book(String isbn, int publicationYear, List reviews) {this.isbn = isbn;this.publicationYear = publicationYear;this.reviews = Lists.newArrayList(reviews);}public Book withIsbn(String isbn) {return new Book(isbn,this.publicationYear, this.reviews);}

請注意,我們添加了一個新的私有構造函數,可以在其中指定每個屬性的值,并修改舊的構造函數以使用新的構造函數。 另外,我們添加了一個新方法,該方法返回一個新的Book對象,該對象具有我們想要的isbn屬性值。 相同的概念適用于該類的其余屬性。 之所以稱為功能方法,是因為方法無需修改即可返回對其參數進行操作的結果。 這與程序命令式方法形成對比,在方法式命令式方法中,方法將一個過程應用于其操作數,從而更改其狀態。

這種生成新對象的方法顯示了不可變類的唯一真正缺點:它們要求我們為所需的每個不同值創建一個新對象,這會在性能和內存消耗方面產生可觀的開銷。 如果要更改對象的幾個屬性,則會放大此問題,因為在每個步驟中都將生成一個新對象,并且最終將丟棄所有中間對象并僅保留最后一個結果。

我們可以在構建器模式的幫助下為多步操作提供更好的選擇,例如我在上一段中描述的操作。 基本上,我們向構建器添加一個新的構造函數,該構造函數采用一個已經創建的實例來設置其所有初始值。 然后,客戶端可以以通常的方式使用構建器來設置所有所需的值,然后使用build方法來創建最終對象。 這樣,我們避免只使用我們需要的某些值來創建中間對象。 在我們的示例中,此技術在生成器方面看起來像這樣:

public BookBuilder(Book book) {this.isbn = book.getIsbn();this.publicationYear = book.getPublicationYear();this.reviews = book.getReviews();
}

然后,在我們的客戶上,我們可以:

Book originalBook = getRandomBook();Book modifiedBook = new BookBuilder(originalBook).isbn('123456').publicationYear(2011).build();

現在,顯然該構建器不是線程安全的,因此您必須采取所有常用的預防措施,例如不與多個線程共享一個構建器。

我提到過這樣一個事實,即我們必須為狀態的每個變化都創建一個新對象,這可能會增加性能,這是不可變類的唯一真正的缺點。 但是,對象創建是JVM不斷改進的方面之一。 實際上,除特殊情況外,對象創建比您想象的要高效得多。 無論如何,提出一個簡單明了的設計通常是一個好主意,然后僅在進行測量后才重構性能。 當您嘗試猜測代碼在何處花費大量時間時,十分之九會發現您錯了。 此外,不變對象可以自由共享而不必擔心后果,這一事實使您有機會鼓勵客戶端盡可能重用現有實例,從而大大減少了創建對象的數量。 一種常見的方法是為最常見的值提供公共靜態最終常量。 此技術在JDK上大量使用,例如在Boolean.FALSEBigDecimal.ZERO中

總結一下這篇文章,如果您想從中學到一些東西,那就這樣吧: 除非有充分的理由使類可變,否則類應該是不可變的 。 不要為每個類屬性自動添加設置器。 如果由于某種原因您絕對不能使您的類不可變,那么請盡可能限制其可變性。 一個對象可以處于的狀態越少,就越容易考慮該對象及其不變量。 而且不必擔心不變性的性能開銷,很有可能您不必擔心它。

參考: JCG合作伙伴 Jose Luis在開發上的 不變性的來龍去脈 。

翻譯自: https://www.javacodegeeks.com/2013/01/the-ins-and-outs-of-immutability.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/370076.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/370076.shtml
英文地址,請注明出處:http://en.pswp.cn/news/370076.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JavaScript總結(3)

第3章 獲取用戶的輸入 &#xff1c;script&#xff1e;10 intAprompt("請輸入第一個數字","");11 intBprompt("請輸入第二個數字",27);默認是2712 document.write("你輸入的第一個數字是"intA);13 document.write("&#xff1c;…

css書寫規范

在書寫css樣式的時候總是無意中就寫亂了&#xff0c;無論是命名或者是樣式的書寫順序&#xff0c;這里做一個總結&#xff0c;提醒自己在書寫css的時候時刻注意&#xff0c;大家可以參考哈。 1. 樣式屬性順序 單個樣式規則下的屬性在書寫時&#xff0c;應按功能進行分組&…

android 協程,關于android:Kotlin協程實現原理SuspendCoroutineContext

明天咱們來聊聊Kotlin的協程Coroutine。如果你還沒有接觸過協程&#xff0c;舉薦你先瀏覽這篇入門級文章What? 你還不曉得Kotlin Coroutine?如果你曾經接觸過協程&#xff0c;置信你都有過以下幾個疑難&#xff1a;協程到底是個什么貨色&#xff1f;協程的suspend有什么作用&…

清空easyui checkbox選中項

$(#dg).datagrid(unselectAll);轉載于:https://www.cnblogs.com/douhuan/p/7116744.html

python 編輯excel需要什么包_Python 中操作EXCEL表格的包

今天&#xff0c;馬云爸爸又來貢獻金句了&#xff0c;比王健林公公一億一個小目標還高&#xff0c;“一個月掙一二十個億很難受&#xff01;&#xff01;&#xff01;”&#xff0c;作為在傳統企業主要為電商部門提供數據分析的數據分析師&#xff0c;體驗太深刻了。雙11前后&a…

用Java處理大文件

最近&#xff0c;我不得不處理一組包含逐筆歷史匯率市場數據的文件&#xff0c;并很快意識到使用傳統的InputStream都無法將它們讀取到內存中&#xff0c;因為每個文件的大小都超過4 GB。 Emacs甚至無法打開它們。 在這種特殊情況下&#xff0c;我可以編寫一個簡單的bash腳本&…

java IO(一):File類

1.File類簡介 File類位于java.io包中。它面向文件層次級別操作、查看文件&#xff0c;而字節流、字符流操作數據時顯然比之更底層。 學習File類包括以下幾個重點&#xff1a;文件路徑、文件分隔符、創建文件(目錄)、刪除文件(目錄)、查看文件內容(輸出目錄內文件)、判斷文件(是…

android listview 開發,android開發之ListView實現

今天又初步學習了一下ListView控件&#xff0c;看看效果如下&#xff1a;LisViewActivity.java源碼&#xff1a;package com.jinhoward.UI_listview;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import android.os.Bundl…

input ios問題 小程序_微信小程序開發常見問題匯總

原標題&#xff1a;微信小程序開發常見問題匯總1、域名必須是https非https的域名不被微信小程序允許。2、input組件placeholder字體顏色卸載placeholder-class里面的color并不生效&#xff0c;需要寫在placeholder-style里面就可以了。3、wx.navigateTo無法跳轉到帶tabbar的頁面…

https://github.com/

https://github.com/ qq郵箱 轉載于:https://www.cnblogs.com/chang1/p/7133251.html

Less 的用法

1. node.js node.js是一個前端的框架 自帶一個包管理工具npm node.js 的安裝 官網&#xff1a;http://nodejs.cn/ 在命令行檢驗是否安裝成功 切換到項目目錄&#xff0c;初始化了一個package.json文件 安裝與卸載jQuery包&#xff08;例子&#xff09; 安裝 卸載 安裝淘寶…

淺談springboot整合ganymed-ssh2遠程訪問linux

環境介紹 技術棧 springbootmybatis-plusmysqlganymed-ssh2 軟件 版本 mysql 8 IDEA IntelliJ IDEA 2022.2.1 JDK 1.8 Spring Boot 2.7.13 mybatis-plus 3.5.3.2 SSH(遠程連接工具)連接原理&#xff1a;ssh服務是一個守護進程(demon)&#xff0c;系統后臺監聽客戶…

優化Neo4j Cypher查詢

上周&#xff0c;我花了很多時間嘗試使用實時系統中的數據來優化大約20個執行失敗的Cypher查詢&#xff08;36866ms至155575ms&#xff09;。 經過一番嘗試和錯誤&#xff0c;以及來自Michael的大量投入&#xff0c;我能夠大致確定對查詢進行哪些操作才能使它們性能更好-最后&a…

python 多文件知識

對于一個大型的項目&#xff0c;會存在很多個py文件&#xff0c;本文記錄與多文件有關的內容。 1. python 如何在一個.py文件中調用另一個.py文件的類 如果是在同一個 module中(也就是同一個py 文件里),直接用就可以如果在不同的module里,例如a.py里有 class A:b.py 里有 class…

android pick file,LFilePicker---文件選擇利器,各種樣式有它就夠了

LFilePicker在 Android 開發中如果需要選擇某個文件&#xff0c;可以直接調取系統的文件管理器進行選擇&#xff0c;但是無法保證各個廠商的手機界面一致&#xff0c;而且解析Uri 還比較繁瑣&#xff0c;如果還需要多選呢&#xff1f;需要文件類型過濾呢&#xff1f;老板說界面…

老筆記整理二:網頁小問題匯總

最近有一些小問題。想在這里寫出來。一是方便大家排錯&#xff0c;再是自己也整理一下。 1。很傻的小問題。。。參數提交方式有一個應該是form而不是from。&#xff08;英語老師&#xff0c;我對不起你。。。&#xff09; 2。用超鏈接傳參數&#xff0c;在&#xff1f;后面不能…

在JVM之下–類加載器

在許多開發人員中&#xff0c;類加載器是Java語言的底層&#xff0c;并且經常被忽略。 在ZeroTurnaround上 &#xff0c;我們的開發人員必須生活&#xff0c;呼吸&#xff0c;飲食&#xff0c;喝酒&#xff0c;并且幾乎與類加載器保持親密關系&#xff0c;才能生產JRebel技術&a…

matplotlib繪制餅狀圖

源自http://blog.csdn.net/skyli114/article/details/77508430?ticketST-41707-PzNbUDGt6R5KYl3TkWDg-passport.csdn.net pyplot使用plt.pie()來繪制餅圖 1 import matplotlib.pyplot as plt 2 labels frogs, hogs, dogs, logs 3 sizes 15, 20, 45, 10 # [15,20,45,10…

自適應寬度元素單行文本省略用法探究

單行文本省略是現代網頁設計中非常常用的技術&#xff0c;幾乎每個站點都會用到。單行文本省略適用于顯示摘要信息的場景&#xff0c;如列表標題、文章摘要等。在響應式開發中&#xff0c;自適應寬度元素單行文本省略容易失效不起作用&#xff0c;對網頁開發這造成困擾。因此&a…

P3390 【模板】矩陣快速冪

題目背景 矩陣快速冪 題目描述 給定n*n的矩陣A&#xff0c;求A^k 輸入輸出格式 輸入格式&#xff1a; 第一行&#xff0c;n,k 第2至n1行&#xff0c;每行n個數&#xff0c;第i1行第j個數表示矩陣第i行第j列的元素 輸出格式&#xff1a; 輸出A^k 共n行&#xff0c;每行n個數&…