1. 程式人生 > >使可變性最小化。

使可變性最小化。

不可變類只是其例項不能被修改的類。每個例項中包含的所有資訊都必須在建立該例項的時候就提供,並在物件的整個生命週期(lifetime)內固定不變。Java平臺類庫中包含許多不可變的類,其中有String、基本型別的包裝類、BigInteger和BigDecimal。存在不可變的類有許多理由:不可變的類比可變的淚更加易於設計、實現和使用。他們不容易出錯,且更加安全。

為了使類成為不可變,要遵循下面五條規則:

1、不要提供任何會修改物件狀態的方法(也稱為mutator),即改變物件屬性的方法。

2、保證類不會被擴充套件。這樣可以防止粗心或者惡意的子類假裝物件的狀態已經改變,從而破壞該類的不可變行為。為了防止子類化,一般做法是使這個類成為final的。

3、使所有的域都是final的。通過系統的強制方式,這可以清楚地表明你的意圖。而且,如果一個指向新建立例項的引用在缺乏同步機制的情況下,從一個執行緒被傳遞到另一個執行緒,就必須確保正確的行為。

4、使所有的域都成為私有的。這樣可以防止客戶端獲得訪問被域引用的可變物件的許可權,並防止客戶端直接修改這些物件。雖然從技術上講,允許不可變的類具有公有的final域,只要這些域包含基本型別的值或者指向不可變物件的引用,但是不建議這樣做,因為這樣會使得在以後的版本中無法再改變內部的表示法。

5、確保對於任何可變元件的互斥訪問。如果類具有指向可變物件的域,則必須確保該類的客戶端無法獲得指向這些物件的引用。並且,永遠不要用客戶端提供的物件引用初始化這樣的域,也不要從任何訪問方法(accessor)中返回該物件引用。在構造器、訪問方法和readObject中請使用保護性拷貝(defensive copy)技術。

大多數重要的不可變類都使用了函式的(functional)做法,因為這些方法返回了一個函式的結果,這些函式對運算元進行運算但並不修改他。與之相對應的更常見的是過程的(procedural)或者命令式的(imperative)做法,使用這些方式時,將一個過程作用在它的運算元上,會導致它的狀態發生改變。

不可變物件比較簡單。不可變物件可以只有一種狀態,即被建立時的狀態。如果你能夠確保所有的構造器都建立了這個類的約束關係,就可以確保這些約束關係在整個生命週期內永遠不再發生變化,你和使用這個類的程式設計師都無需再做額外的工作來維護這些約束關係。另一方面,可變的物件可以有任意複雜的狀態空間。如果文件中沒有對mutator方法所執行的狀態轉換提供精確地描述,要可靠地使用一個可變類是非常困難的,甚至是不可能的。

不可變物件本質上是執行緒安全的,他們不要求同步。當多個執行緒併發訪問這樣的物件時,他們不會遭到破壞。這無疑是獲得執行緒安全最容易的辦法。實際上,沒有任何執行緒會注意到其他執行緒對於不可變物件的影響。所以,不可變物件可以被自由地共享。不可變類應該充分利用這種優勢,鼓勵客戶端儘可能地重用現有的例項。要做到這一點,一個很簡單的辦法就是,對於頻繁使用的值,為他們提供公有的靜態final常量。

這種方法可以被進一步擴充套件。不可變的類可以提供一些靜態工廠,他們把頻繁被請求的例項快取起來,從而當現有例項可以符合請求的時候,就不必建立新的例項。所有基本型別的包裝類和BigInteger都有這樣的靜態工廠。使用這樣的靜態工廠也使得客戶端之間可以共享現有的例項,而不用建立新的例項,從而降低記憶體佔用和垃圾回收的成本。在設計新的類時,選擇用靜態工廠代替公有的構造器可以讓你以後有新增快取的靈活性,而不必影響客戶端。

“不可變物件可以被自由的共享”導致的結果是,永遠也不需要進行保護性拷貝。實際上,你根本不需做任何拷貝,因為這些拷貝始終等於原始的物件。因此,你不需要也不應該為不可變的類提供clone方法或者拷貝構造器(copy constructor)。

不僅可以共享不可變物件,甚至也可以共享他們的內部資訊,例如BigInteger類內部使用了符號數值表示法。符號用一個int型別的值來表示,數值則用一個int陣列表示。negate方法產生一個新的BigInteger,其中數值是一樣的,符號則是相反的。他並不需要拷貝陣列;新建的BigInteger也指向原始例項中的同一個內部陣列。

不可變物件為其他物件提供了大量的構件(building blocks),無論是可變的還是不可變的物件,如果知道一個複雜物件內部的元件物件不會改變,要維護他的不變性約束是比較容易的。這條原則的一種特例在於,不可變物件構成了大量的對映鍵(map key)和集合元素(set element);一旦不可變物件進入到對映(map)或者集合(set)中,儘管這破壞了對映或者集合的不變性約束,但是也不用擔心他們的值會發生變化。

不可變類真正唯一的缺點是,對於每個不同的值都需要一個單獨的物件,建立這種物件的代價可能很高,特別是對於大型物件的情形。

如果你執行一個多步驟的操作,並且每個步驟都會產生一個新的物件,除了最後的結果之外其他的物件最終都會被丟棄,此時效能問題就會顯露出來。處理這種問題有兩種辦法。第一種辦法,先猜測一下會經常用到哪些多步驟的操作,然後將他們作為基本型別提供。如果某個多步驟操作已經作為基本型別提供,不可變的類就可以不必在每個步驟單獨建立一個物件。不可變的類在內部可以更加靈活。例如,BigInteger有一個包級私有的可變“配套類(companing class)”,它的用途是加速諸如“模指數(modular exponentiation)”這樣的多步驟操作。

如果能夠精確地預測出客戶端將要在不可變的類上執行哪些複雜的多階段操作,這種包級私有的可變配套類的方法就可以工作的很好。如果無法預測,最好的辦法是提供一個公有的可變配套類。在Java平臺類庫中,這種方法的主要例子是String類,它的可變配套類時StringBuilder(和基本上已經廢棄的StringBuffer)。

除了“使類成為final的”這種方法之外,還有另外一種更加靈活的方法可以做到這一點。讓不可變的類變成final的另一種辦法就是,讓類的所有構造器都變成私有的或者包級私有的,並新增公有的靜態工廠(static factory)來代替公有的構造器。

雖然這種方法並不常用,但它經常是最好的替代方法。它最靈活,因為它允許使用多個包級私有的實現類。對於處在它的包外部的客戶端而言,不可變的類實際上是final的,因為可能把來自另一個包的類,缺少公有的或受保護的構造器的類進行擴充套件。除了允許多個實現類的靈活性之外,這種方法還使得很可能通過改善靜態工廠的物件快取能力,在後續的發行版本中改進該類的效能。

沒有一個方法能夠對物件的狀態產生外部可見(externally visible)的改變。然而,許多不可變的類擁有一個或者多個非final的域,他們在第一次被請求執行這些計算的時候,把一些開銷昂貴的類擁有一個或者多個非final的域,他們在第一次被請求執行這些計算的時候,把一些開銷昂貴的計算結果快取在這些域中。如果將來再次請求同樣地計算,就直接返回這些快取的值,從而解決了重新計算所需要的開銷。這種技巧可以很好地工作,因為物件是不可變的,它的不可變性保證了這些計算如果被再次執行,就會產生同樣地結果。

有關序列化功能的一條告誡有必要在這裡提出來。如果你選擇讓自己的不可變類實現Serializable介面,並且他包含一個或者多個指向可變物件的域,就必須提供一個顯式的readObject或者readResolve方法,或者使用ObjectOutputStream.writeunshared和ObjectInputStream.readUnshared方法,即使預設的序列化形式是可以接受的,也是如此。否則攻擊者可能從不可變的類建立可變的例項。

總之,堅決不要為每個get方法編寫一個相應的set方法。除非有很好地理由要讓類成為可變的淚,否則就應該是不可變的。不可變的類有許多優點,唯一缺點是在特定的情況下存在潛在的效能問題。

對於有些類而言,其不可變性是不切實際的。如果類不能被做成不可變的,仍然應該儘可能地限制他的可變性。降低物件可以存在的狀態數,可以更容易的分析該物件的行為,同時降低出錯的可能性。因此,除非有令人信服的理由要使域變成是非final的,否則要使每個域都是final的。

構造器應該建立完全初始化的物件,並建立其所有的約束關係。不要在構造器或者靜態工廠之外再提供公有的初始化方法,除非有令人信服的理由必須這麼做。同樣地,也不應該提供“重新初始化”方法(它使得物件可以被重用,就好像這個物件是由另一不同的初始狀態構造出來一樣)。與所增加的複雜性相比,“重新初始化”方法通常並沒有帶來太多的效能優勢。

可以通過TimeTask類來說明這些原則。它是可變的,但是他的狀態空間被有意的設計的非常小。你可以建立一個例項,對它進行排程使他執行起來,也可以隨意的取消它。一旦一個定時器任務(time task)已經完成,或者已經被取消,就不可能再對它重新排程。

相關推薦

使可變性

不可變類只是其例項不能被修改的類。每個例項中包含的所有資訊都必須在建立該例項的時候就提供,並在物件的整個生命週期(lifetime)內固定不變。Java平臺類庫中包含許多不可變的類,其中有String、基本型別的包裝類、BigInteger和BigDecimal。存在不可變

使可變性

不可變類存在有許多理由:不可變類比可變類更加易於設計,實現和使用,不容易出錯,且更加安全 不可變類規則 不要提供任何會修改物件狀態的方法 保證類不會被擴充套件 是所有的域都是final的 使所有的域都稱為私有的 確保對於任何可變元件的互斥訪問 不可變類

Effective Java 第十五條:使可變性

複數類Complex: public final class Complex { private final double re; private final double im;

Effective Java讀書筆記-使可變性

存在不可變類的原因:不可變的類比可變的類更加易於設計、實現和使用。它們不容易出錯且更加安全。 使類變為不可變需要遵守的五項原則: 不要提供任何會修改物件狀態的方法。 保證類不會被拓展。這樣可以防止粗心或者惡意的子類假裝物件的狀態已經改變,從而破壞類的不

effective java(15) 之使可變性

effective java 之使可變性最小化 1、不可變類是例項不能被修改的類。每個例項中包含的所有資訊都必須在建立該例項的時候就提供,並在物件的整個生命週期內固定不變。例如String、BigInteger和BigDecimal類。不可變類更易於設計、實現和使用。 2

Effective Java -- 使可變性

保護 uri 客戶端 可變對象 final ive 方法 擴展 java 為了使類成為不可變的,應該遵循以下五條原則: 1. 不要提供任何會下蓋對象狀態的方法 2. 保證類不會被擴展 3. 使所有的域都是final的 4. 使所有的域都成為私有的 5. 確保對於任

給定一個填充非負數的m×n網格,找到一條從左上到右下的路徑,這個路徑將所有數字的總和

本題源自leetcode  64 ------------------------------------------------------------------- 思路 : 動態規劃 1 用一個二維陣列dp[i][j]記錄到達 i,j 所需要的最小路徑和。考慮邊界條件

在MFC視窗中畫圖,如何使視窗後圖形不消失

我遇到的問題:在MFC的視窗中畫圖,如何使最小化後圖形不消失?在mfc的視窗中畫圖形,但當這個視窗被遮蔽覆蓋或最小化後,圖就消失了,如何能使視窗還原後圖形依然顯示。解決辦法:新增對WM_PAINT訊息的處理,也就是加上OnPaint函式,把畫圖的程式碼放到這個函式中,這樣就可

第十三條 類和成員可變性

        java是開源的,三大特徵之一就是封裝,好的封裝可以讓開發者感到愉悅,壞的封裝就不說了。一個模組的設計好與不好,封裝顯得特別重要。如果一個模組的細節全都在自己模組內部處理完畢,外部需要呼叫這個模組,只需要傳入引數即可,不必自己又去呼叫內部的各個函式,進行邏輯判

第十五條:可變性

<span style="font-size:18px;">public class Point{ //設定為final,初始化之後無法改變 private final int x; private final int y; publi

序列劃分-使大值

new 最大的 style return blog content file 一份 -m 問題:給指定的序列劃分為3份。取每份的最大值,再從3份最大值取出最大的max。怎樣劃分,能夠使max最小?輸入:1,2,3,4,5劃分:1,2,3 | 4 |5最大值為6思路:兩個

使類和成員的可訪問性

線程 protect 父類 最小化 數組 受保護 強制 可變對象 bsp 信息隱藏的概念:模塊之間只通過API交互,互相不知道內部工作狀況 JAVA提供用於信息隱藏的機制:訪問控制 第一規則:盡可能使每個類或類成員不被外界訪問 四種可能的訪問級別:   1. 私有的 pri

Effective Java 第三版——15. 使類和成員的可訪問性

control 常見 以及 操作 數據表示 potential info 四大 access Tips 《Effective Java, Third Edition》一書英文版已經出版,這本書的第二版想必很多人都讀過,號稱Java四大名著之一,不過第二版2009年出版,到

《Effective Java》讀書筆記 - 類的可變性

有效 down private 讀書筆記 工廠 public 限定 如果 構造器 Item 15 最小化類的可變性 effective java 如何做到讓類不可變? 不提供改變對象狀態的方法。(mutators) 確保類不會被繼承,即用final來限定類。 讓所有的

關於virulbox克隆了centos6.0安裝後如何使網卡能夠上網

virulbox克隆系統網卡上網解決辦法: So here’s how we fix it:Remove the kernel’s networking interface rules file so that it can be regenerated # rm -f /etc/udev/rules.d/7

MFC 主對話方塊到托盤,托盤點選還原主對話方塊

1.將主對話方塊資源屬性Minimize Box設定為true,這個屬性顯示對話方塊的最小化按鈕。 2.最小化到托盤。 (1)首先要了解,你工作列右下角的托盤都是圖示,所以最小化到托盤建議新增一個Icon資源。 (2)到托盤的函式需要自己重構。 先巨集定義 #define

VS2010編寫MFC程式,調整視窗大小和實現視窗最大化、方法

首先開啟MFC視窗編輯,在右側屬性一欄會找到Border、Maximize Box和Minimize Box三個選項,將Border改為Resizing,Maximize Box改為True,Minimize Box改為True,那麼你編寫的MFC視窗就可以實現改變大小和最

Effective Java 3rd 條目17 可變性

一個不可變類簡單地講是例項不可以改變的一個類。在每個例項裡面包含的所有資訊在物件的生命週期裡面是確定的,所以從來不會看到改變。Java平臺庫包含了許多不可變類,包括String、原始裝箱型別和BigInteger與BigDecimal。為此有很多很好的原因:不可變類比可變類更容易設計、實現和

swing 自定義按鈕後,實現點選工作列圖示,使窗體重新顯示

jf.setUndecorated(true); // 去掉視窗的裝飾 jf.getRootPane().setWindowDecorationStyle(JRootPane.NONE)

C# 如何使自己的程式在“顯示桌面”時不

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.T