91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中怎么實現一個不可變類

發布時間:2021-07-24 15:14:59 來源:億速云 閱讀:123 作者:Leah 欄目:編程語言

Java中怎么實現一個不可變類,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

在 Java 中實現不可變類

諸如 Java、Ruby、Perl、Groovy 和 C# 一類的現代面向對象語言都擁有一些內置的便利機制,這些機制使得以可控方式來修改狀態變得很容易。然而,狀態對于計算來說是如此基礎的信息,因此您永遠也無法預料它會在哪個地方出紕漏。例如,由于大量可變化機制的存在,因此用面向對象的語言編寫高性能的、正確的多線程代碼會很困難。因為 Java 已針對操縱狀態進行了優化,因此您不得不繞過這樣的一些機制來獲得的不變性的一些好處。不過一旦您了解了要避免的一些陷阱之后,在 Java 中構建不可變類這件事情就會變得非常容易。

定義不可變類

要將一個 Java 類構造成不可變的類,您必須執行以下操作:

把所有的域聲明成 final。

在 Java 中將域定義成 final 之后,您必須在聲明的時候初始化它們,或是在構造函數中初始化它們。如果您的 IDE 抱怨您沒有在聲明的時候初始化它們,別緊張;當您在構造函數中寫入適當的代碼后,他們就會意識到您知道自己在做什么。

將類聲明為 final,這樣就不會重寫它。

如果可以重寫類的話,則可以重寫它的方法的行為,因此您最安全的選擇就是不允許將類子類化。注意,這就是 Java 的 String 類使用的策略。

不要提供一個無參數的構造函數。

如果您有一個不可變對象,則必須要在構造函數中設置該對象將包含的任何狀態。如果沒有狀態要設置,那么要一個對象來干什么?無狀態類的靜態方法一樣可以起到很好的作用;因此,您永遠都不該為一個不可變類提供一個無參數的構造函數。如果您正在使用的框架因為某些原因需要使用這樣的構造函數的話,那么您可以了解以下能否通過提供一個私有的無參數構造函數(這是經由反射可見的)來滿足這一要求。

需要注意的一點是,無參數構造函數的缺失違反了 JavaBeans 的標準,該標準堅持要有一個默認的構造函數。不過 JavaBeans 無論如何都不可能是不可變的,這是 setXXX 方法的工作方式所決定的。

至少提供一個構造函數。

如果您沒有提供一個無參數構造函數的話,那么這是您給對象添加一些狀態的最后機會!

除構造函數之外,不再提供任何的可變方法。

您不僅要避免典型的受 JavaBeans 啟發的 setXXX 方法,還必須注意不要返回可變的對象引用。對象引用被聲明為 final,這是實情,但這并不意味這您無法更改它所指向的內容。因此,您需要確保您已經防御性地復制了從 getXXX 方法中返回的任何對象引用。

“傳統的” 不可變類

清單 1 中列出了一個滿足上述要求的不可變類:

清單 1. Java 中的不可變的 Address 類

public final class Address {private final String name;private final List<String> streets;private final String city;private final String state;private final String zip;public Address(String name, List<String> streets, String city, String state, String zip) {this.name = name;this.streets = streets;this.city = city;this.state = state;this.zip = zip;}public String getName() {return name;}public List<String> getStreets() {return Collections.unmodifiableList(streets);}public String getCity() {return city;}public String getState() {return state;}public String getZip() {return zip;}}

需要注意的一點是,可以使用 清單 1 中的 Collections.unmodifiableList() 方法對 streets 列表進行防御性復制。您應該始終使用集合而不是數組來創建不可變列表,盡管防御性的數組復制也是可行的,但這會帶來一些不希望見到的副作用。考慮一下清單 2 中的代碼:

清單 2. 使用數組而非集合的 Customer 類

public class Customer {public final String name;private final Address[] address;public Customer(String name, Address[] address) {this.name = name;this.address = address;}public Address[] getAddress() {return address.clone();}}

在您嘗試著在從 getAddress() 方法調用中返回的克隆數組上進行任何操作的時候,清單 2 中的代碼問題就暴露出來了,如清單 3 所示:

清單 3. 展示了正確但非直觀結果的測試

public static List<String> streets(String... streets) {return asList(streets);}public static Address address(List<String> streets, String city, String state, String zip) {return new Address(streets, city, state, zip);}@Test public void immutability_of_array_references_issue() {Address [] addresses = new Address[] {address(streets("201 E Washington Ave", "Ste 600"), "Chicago", "IL", "60601")};Customer c = new Customer("ACME", addresses);assertEquals(c.getAddress()[0].city, addresses[0].city);Address newAddress = new Address(streets("HackerzRulz Ln"), "Hackerville", "LA", "00000");// doesn't work, but fails invisiblyc.getAddress()[0] = newAddress;// illustration that the above unable to change to Customer's addressassertNotSame(c.getAddress()[0].city, newAddress.city);assertSame(c.getAddress()[0].city, addresses[0].city);assertEquals(c.getAddress()[0].city, addresses[0].city);}

在返回一個克隆數組的時候,您保護了底層的數組,但您交還的數組看起來就像是一個普通的數組,也就是說,您可以修改該數組的內容(即使持有該數組的變量是 final,因為這只在數組引用自身上起作用,在非數組的內容上不起作用)。在使用 Collections.unmodifiableList() (以及 Collections 中用于其他類型的一系列方法)時,您會收到一個對象引用,它沒有改變方法的可用性。

更清晰的不可變類

您可能經常聽到這樣的說法:您還應該將不可變域聲明為私有域。在聽到有人以一種不同的、但明確的看法來澄清一些根深蒂固的臆斷之后,我不再同意這樣的觀點了。在 Michael Fogus 對 Clojure 的創建者 Rich Hickey 所做的訪談中),Hickey 談到了 Clojure 的許多核心部分都缺少數據隱藏式的封裝。Clojure 在這一方面一直困擾著我,因為我是如此沉迷基于狀態的思考方式。

但在那之后,我意識到了,如果域是不可變的話,則無需擔心它們被暴露出來。許多我們用在封裝中的保障措施實際上就是為了防止發生改變,一旦我們梳理清楚了這兩個概念,一種更清晰的 Java 實現就浮現了出來。

請考慮清單 4 中的 Address 類版本:

清單 4. 使用了公共不可變域的 Address 類

public final class Address {private final List<String> streets;public final String city;public final String state;public final String zip;public Address(List<String> streets, String city, String state, String zip) {this.streets = streets;this.city = city;this.state = state;this.zip = zip;}public final List<String> getStreets() {return Collections.unmodifiableList(streets);}}

在您想要隱藏底層表示形式的時候,只有為不可變域聲明公共的 getXXX() 方法才會帶一些好處,但在重構期間會有一些顯而易見的好處,比如可以很容易地發現細微的改變。通過將域聲明成公共的或是不可變的,就能夠直接在代碼中訪問它們,無需擔心不小心更改它們的情況發生。

一開始的時候,使用不可變域似乎有些不自然,如果您聽過 憤怒的猴子 這個故事的話,就會知道這種不同其實是有好處的:您還不習慣于處理 Java 中的不可變類,這看起來像是一種新的類型,如清單 5 中所示:

清單 5. Address 類的單元測試

@Test (expected = UnsupportedOperationException.class)public void address_access_to_fields_but_enforces_immutability() {Address a = new Address(streets("201 E Randolph St", "Ste 25"), "Chicago", "IL", "60601");assertEquals("Chicago", a.city);assertEquals("IL", a.state);assertEquals("60601", a.zip);assertEquals("201 E Randolph St", a.getStreets().get(0));assertEquals("Ste 25", a.getStreets().get(1));// compiler disallows//a.city = "New York";a.getStreets().clear();}

對公有不可變域的訪問避免了一系列 getXXX() 調用所帶來的可見開銷,還要注意的是,編譯器不會允許您給這些原始類型中的任一個賦值,如果您試著調用 street 集合上的可變方法的話,您就會收到一個 UnsupportedOperationException (方式是在測試的頂部捕獲)。這種代碼風格的使用從視覺上給出了一種強烈的指示:該類是一個不可變類。

不利的方面

這種更清晰的語法的一個可能缺點是需要花一些精力來學習這種新的編程技法,不過我覺得這樣做是值得的:這一過程會促進您在創建類的時候想著不變性,因為類的風格是如此明顯不同,并且刪除了不必要的樣板代碼。不過 Java 中的這種代碼風格也有著一些缺點(說句公道話,Java 的直接目的從來都不是為了迎合不變性):

1.正如 Glenn Vanderburg 向我指出的那樣,最大的缺點是這一風格違反了 Bertrand Meyer(Eiffel 編程語言的創建者)所說的統一訪問原則 (Uniform Access Principle):模塊提供的所有服務應該是通過一種統一的標記法來使用的,無論服務是通過存儲還是通過計算來實現的,都不能違背這種標記法。換句話說,對域的訪問不應該暴露出該域是一個域還是一個返回值的方法。Address 類的 getStreets() 方法與其他域沒有保持統一。這一問題在 Java 中不可能得到真正的解決;但在其他的一些 JVM 語言中已經通過實現不變性解決了這個問題。

2.一些重度依賴反射的框架無法使用這種編程技法來工作,因為他們需要一個默認的構造函數。

3.因為您是創建新的對象而不是改變原有的對象,因此有著大量更新的系統可能就會導致以為垃圾收集而帶來的效率低下。Clojure 一類的語言內置了一些工具,通過使用不可變引用將這種情況變得更高效一些,這是這些語言中的默認做法。

Groovy 中的不可變性

可以使用 Groovy 來構建公共的不可變域版本的 Address 類,這帶來的是一種非常清晰的實現,如清單 6 所示:

清單 6. 使用 Groovy 編寫的不可變的 Address 類

class Address {def public final List<String> streets;def public final city;def public final state;def public final zip;def Address(streets, city, state, zip) {this.streets = streets;this.city = city;this.state = state;this.zip = zip;}def getStreets() {Collections.unmodifiableList(streets);}}

一如既往,Groovy 需要的樣板代碼要比 Java 的少,并且還提供了其他方面的一些好處。因為 Groovy 允許您使用熟悉的 get/set 語法來創建屬性,因此您可以為對象引用創建真正受保護的屬性。考慮一下清單 7 中給出的單元測試:

清單 7. 單元測試展示了 Groovy 中的統一訪問

class AddressTest {@Test (expected = ReadOnlyPropertyException.class)void address_primitives_immutability() {Address a = new Address(["201 E Randolph St", "25th Floor"], "Chicago", "IL", "60601")assertEquals "Chicago", a.citya.city = "New York"}@Test (expected=UnsupportedOperationException.class)void address_list_references() {Address a = new Address(["201 E Randolph St", "25th Floor"], "Chicago", "IL", "60601")assertEquals "201 E Randolph St", a.streets[0]assertEquals "25th Floor", a.streets[1]a.streets[0] = "404 W Randoph St"}}

可以注意到,在這兩個用例中,測試會在拋出異常時終止,這是因為有語句違反了不可變性合約。不過在 清單 7 中,streets 屬性看起來就像是原始類型,但實際上它是用自己的 getStreets() 方法來保護其自身。

Groovy 的 @Immutable 注釋

本文章系列所持的一個基本宗旨就是,函數式語言應該為您處理更多低層面的細節。一個很好的例子就是 Groovy 的 1.7 版本增加了 @Immutable 注解,該注解使得 清單 6 中的編碼方式變得不再重要了。清單 8 給出了一個使用了該注解的 Client 類:

清單 8. 不可變的 Client 類

@Immutableclass Client {String name, city, state, zipString[] streets}

因為用到了 @Immutable 注解,該類具有以下一些特點:

它是最終的。  屬性自動擁有了私有的、合成了 get 方法的域。  任何更新屬性的企圖都會導致拋出 ReadOnlyPropertyException 異常。  Groovy 既創建了有序的構造函數,又創建了基于映射的構造函數。  集合類被封裝在適當的包裝器中,數組(及其他可克隆的對象)被克隆。  自動生成默認的 equals、hashcode 和 toString 方法。

一句注解提供了這么多的作用!它的行為也正如您所期望的那樣,如清單 9 所示:

清單 9. @Immutable 注解正確地處理了預期的情況

@Test (expected = ReadOnlyPropertyException)void client_object_references_protected() {def c = new Client([streets: ["201 E Randolph St", "Ste 25"]])c.streets = new ArrayList();}@Test (expected = UnsupportedOperationException)void client_reference_contents_protected() {def c = new Client ([streets: ["201 E Randolph St", "Ste 25"]])c.streets[0] = "525 Broadway St"}@Testvoid equality() {def d = new Client([name: "ACME", city:"Chicago", state:"IL",zip:"60601",streets: ["201 E Randolph St", "Ste 25"]])def c = new Client([name: "ACME", city:"Chicago", state:"IL",zip:"60601",streets: ["201 E Randolph St", "Ste 25"]])assertEquals(c, d)assertEquals(c.hashCode(), d.hashCode())assertFalse(c.is(d))}

試圖重置對象引用的操作會導致拋出 ReadOnlyPropertyException 異常。如果試圖改變其中的一個被封裝起來的對象引用所指向的內容,則會導致拋出 UnsupportedOperationException 異常。該注解還創建了適當的 equals 和 hashcode 方法,如最后一個測試中所示,對象內容是相同的,但它們沒有指向同一個引用。

當然,Scala 和 Clojure 都支持并促進了不變性,且都有著清晰的不變性語法,接下來的文章會不時地談到它們所帶來的影響。

不變性的好處

在像函數式編程者那樣思考的方法列表中,維護不變性處于列表的較高位置。盡管用 Java 來構建不可變對象前期帶來了更多的復雜性,但由這種抽象帶來的后期簡易性很容易補償前面所做的工作。

不可變類擯棄了 Java 中許多一些典型的令人煩心的缺陷。轉向函數式編程的好處之一是讓人們意識到,測試的存在是為了檢查代碼中成功發生的轉變。換句話說,測試的真正目的是驗證改變,改變越多,就需要越多的測試來確保您的做法是正確的。如果您通過嚴格限制改變來隔離變化的發生,那么您為錯誤的發生制造了更小的空間,需要測試的地方也就更少。因為變化只會發生構造函數中,因此不可變類會將編寫單元測試變成了一件微不足道的事情。

您不需要使用復制構造函數,并且永遠也不需要大汗淋漓地去實現 clone() 方法的那些令人慘不忍睹的細節。將不可變對象用作 Map 或是 Set 中的鍵值是也一種很不錯的選擇;因為 Java 的字典集合中的鍵不能更改值,因此,在將不可變對象用作鍵時,它是非常好用的鍵。

不可變對象也是自動線程安全的,不存在同步問題。它們也不可能因為異常的發生而處于一種未知的或是無法預期的狀態中。因為所有的初始化都發生在構造階段,這在 Java 中是一個原子過程,所有異常都發生在擁有對象實例之前。Joshua Bloch 將這稱作失敗的原子性:在已經構建對象后,這種基于不可變性的成功或是失敗就是一錘定音的了

看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

夏津县| 西华县| 股票| 驻马店市| 乐平市| 介休市| 仁寿县| 都匀市| 巴东县| 柏乡县| 新邵县| 南皮县| 沙洋县| 余庆县| 栾城县| 蓬莱市| 普格县| 贵德县| 永和县| 崇明县| 滁州市| 繁昌县| 岳西县| 平和县| 海安县| 油尖旺区| 成安县| 沙田区| 滦平县| 革吉县| 阳东县| 监利县| 哈巴河县| 平南县| 樟树市| 吴忠市| 兴宁市| 迁西县| 兖州市| 花莲县| 湖口县|