1. 程式人生 > >Effective Java 學習筆記 (六)

Effective Java 學習筆記 (六)

第八條改寫equals時總是要改寫hashCode

每個改寫了equals方法的類中,你必須也要改寫hashCode方法。 hashCode約定的內容: 1.在一個應用程式執行期間,如果一個物件的equals方法做比較所用到的資訊沒有被修改的話,則對該物件呼叫hashCode方法多次,它必須返回始終如一的同一個整數。 2.如果兩個物件根據equals(Object)方法是相等的,這兩個物件所產生的hashCode也相等。 3.如果兩個物件根據equals(Object)方法是不相等的,hashCode最好不等。 生成hashCode的處方: 1. 把某個非零常數值,儲存在一個叫result的int變數中。 2. 
對物件中每一個關鍵域f (指equals方法中考慮的每一個域),完成: a. 為該域計算int型別的雜湊碼c: i.如果該域是boolean型別,則計算(f ? 0 : 1)。 ii.byte、char、short or int型別,則計算(int)f。 iii.long: 計算(int) ( f ^ (f >>> 32) )。 iv.float: 計算Float.floatToIntBits(f)。 v.double:計算 Double.doubleToIntBits(f)得到一個long型別的值,然後按步驟iii 對該long型進行雜湊計算。 vi.物件引用:若該類的equals通過遞迴呼叫equals來比較這個域,則同樣對這個域遞迴呼叫 hashCode。如果要求更復雜的比較,則為這個域計算一個“規範表示”,然後對正規化表示呼叫hashCode。如果這個值為null,則返回0。 vii. 
陣列:吧每個元素當作單獨的域來處理。然後按b中做法把雜湊值組合起來。 b. 把步驟a中計算得到的雜湊碼c組合到result中: result = 37*result + c (選用37是因為它是一個奇素數) 3. 返回result。 4. 寫完hashCode檢查是否相等的例項具有相等的雜湊碼。 如果一個類是非可變的,並且計算雜湊碼的代價也很大,則考慮把雜湊快取在物件內部,而不是每次請求的時候都重新計算雜湊碼。如果覺得此類的大多數物件會被用做雜湊鍵,那麼應該在例項被建立的時候就計算雜湊碼。否則,可選擇“遲緩初始化”雜湊碼,一直到hashCode被第一次呼叫的時候才初始化。 privatevolatile
iint hashCode = 0 ; publicint hashCode(){ if(hashCode == 0){ int result = 17;            result = 37*result + areaCode;            result = 37*result + exchange;            result = 37*result + extension;            hashCode = result;        } return hashCode;     } 不要試圖從雜湊碼計算中排除掉一個物件的關鍵部分以提高效能。 第九條:總是要改寫toString java.lang.Object提供的toString方法,一般返回類的名字@雜湊碼的無符號十六進位制表示。toString的約定建議所有的子類都改寫這個方法。 提供一個好的toString實現,可以使一個類用起來更加愉快。 在實際應用中,toString方法應該返回物件中包含的所有令人感興趣的資訊。 實現toString的時候,必須決定是否在文件中制定返回值的格式。對於值類,推薦這樣做。好處是可以被用做一種標準的、無二義性的、適合人閱讀的物件表達形式。一個好的做法是同時提供一個相匹配的String建構函式或者靜態工廠,這樣可以很容易地在物件和它的字串表示之間來回轉換。 指定格式的不足:如果這類已經被廣泛使用了,則一旦指定了格式,必須堅持這種格式。 無論是否決定指定格式,都應該在文件中明確地表明你的意圖最好為toString返回值中包含的所有資訊,提供一種變成訪問途徑。 第十條:謹慎地改寫clone Cloneable介面的目的是作為物件的一個mixin 介面,表明這樣的物件允許克隆。可它並沒成功達到這個目的,因為Object的clone方法是被保護的,如果不借助於映像機制(reflection 35條),則不能僅僅因為一個物件實現了Cloneable就可以呼叫clone方法。即使映像呼叫也可能失敗,因為不能保證該物件一定具有可訪問的clone方法。 Cloneable決定了Object中受保護的clone方法實現的行為:如果一個類實現了Cloneable,則Object的clone方法返回該物件的逐域拷貝,否則丟擲CloneNotSupportedException異常。這種用法不值得仿效。 為了實現一種語言之前的機制:無須呼叫建構函式就可以建立一個物件。Clone方法需要遵守下面的約定: 建立和返回該物件的一個拷貝,這裡的“拷貝”的精確含義取決於該物件的類。 x.clone() != x ; x.clone().getClass() == x.getClass() ; x.clone().equals(x) == true 但這些都不是絕對要求。 如果改寫了一個非final類的clone方法,則應該返回一個通過呼叫super.clone二得到的物件。這種機制大致類似於自動的建構函式鏈,不過它不是強制要求的。 對於實現了Cloneable的類,我們總期望它也提供一個功能適當的公有clone方法。但通常,除非該類的所有超類都提供了一個行為良好的clone實現,否則是不可能的。 假設你的超類都提供了行為良好的clone方法,你從super.clone()中得到的物件可能會接近於最終要返回的物件,也可能相去甚遠,這取決於類的本質。從超類的角度看,這個物件將是原始物件功能完整的克隆。在這個類中宣告的域將等同於被克隆物件中相應的域值。如果每個域包含原語型別值或者非可變物件的引用,返回的物件可能是你需要的物件。然而,如果物件中包含的域引用了可變的物件,或者代表序列號活其他唯一ID值的域,或者代表物件建立時間的域,那麼使用super.clone可能會導致災難性後果。          clone方法是另一個建構函式;你必須確保它不會傷害到原始的物件,並且正確地建立起被克隆物件中的約束關係。 public Object clone() throws CloneNotSupportedException{        Stack result = (Stack) super.clone();        result.elements = (Object[]) elements.clone(); //遞迴地呼叫clone return result;     } clone結構與指向可變物件的final域的正常用法是不相容的。這裡如果elementsfinal的,則這種方案不能正常工作。除非在原始物件和克隆物件之間可以安全地共享此可變物件。 如果你在為一個散列表編寫clone方法,它的內部資料是由一個雜湊桶陣列組成,每一個雜湊都指向“健-值”對連結串列的第一個條目。為了實現這個類的clone,必須每個組成桶的連結串列單獨地拷貝,深度拷貝:            Entry deepCopy(){            Entry result = new Entry(key,value,next); for(Entry p = result; p.next!=null;p=p.next)               p.next = new Entry(p.next.key,p.next.value,p.next.next); return result;        } 克隆複雜物件的最後一個方法是,先呼叫super.clone,然後把結果物件中的所有域都設定到它們的空白狀態,然後呼叫高層的方法來重新產生物件的狀態,比如 HashTable中的put(key,value)的方法。這種做法執行起來沒有直接操作物件和其克隆的內部狀態的clone方法快。 如同建構函式一樣,clone方法不應該在構造過程中,呼叫新物件中任何非final方法。因此,上一段的put(key,value)要麼是final的,要麼是私有的。 如果一個可擴充套件類改寫了clone方法,那麼改寫版本的clone方法應包含CloneNotSupportedException異常。這樣做可以使子類通過提供下面的clone方法,選擇溫和地放棄克隆能力: publicfinal Object clone() throws CloneNotSupportedException{ thrownew CloneNotSupportedException();     } 另一個實現物件拷貝的好辦法是提供一個拷貝建構函式。          public Yum(Yum yum);          public static Yum newInstance(Yum yum); 它們不依賴於某一種很有風險的、語言之外的物件建立機制;它們不要求遵守尚未良好文件化的規範;它們不會與final域的正常使用發生衝突;它們不會要求客戶捕獲不必要的被檢查異常;它們為客戶提供了一個靜態型別化的物件。