《Effective Java》第2章 對全部對象都通用的方法

第10條:覆蓋equals時,請遵照通用約定

  一、使用==來比較兩個對象的時候,比較的是兩個對象在內存中的地址是否相同(兩個引用指向的是否爲同一個對象);Object中定義的equals方法也是這樣比較的;java

  二、當咱們自定義類的時候,若是不覆蓋equals方法,那麼就會使用默認的equals方法(Object中已經定義了),這樣的話,只有當對象與對象自身比較纔會返回true(指定同一個對象);安全

// Object中的equals,只有對象與對象自身比較纔會返回true
public boolean equals(Object obj) {
    return (this == obj);
}

  三、非自定義類(Java內置類)中進行比較的時候,不必定會使用Object.equals方法,由於內置類可能覆蓋了equals方法,好比Integer的equals方法:框架

// Integer 中的equals方法,比較的兩個對象中的「值」,而非原始的比較兩個地址
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

  四、覆蓋equals方法,是由於咱們要進行判斷「邏輯相等」,好比兩個對象的哪些屬性相等,而不是簡單判斷是否是同一個對象;ide

  五、覆蓋equals時的規範:一、自反性;二、對稱性;三、傳遞性;四、一致性;五、與null判斷相等時,結果必需是false;工具

  六、當在擴展類的時候(好比建立子類,也許會增長新字段),那麼這個時候很難保留上面的equals規範;性能

  七、實現equals方法的訣竅:this

    a:使用「==」檢查「參數是否爲這個對象的引用」spa

    b:使用instance檢查是否爲正確的類型;.net

    c:instance檢查經過後,將參數轉換爲正確的類型;線程

    d:根據業務需求檢查參數中的某些字段是否與該對象中的字段相匹配,而且在比較的時候,優先匹配最有可能不一致的域;

  八、覆蓋equals方法的時候,老是要覆蓋hashCode方法;

  九、不要太過依賴equals方法進行比較,徹底可使用if elseif  else 來進行逐項匹配;

  十、推薦寫法是x.equals(Object o),可是不要將equals方法的參數替換爲其餘類型,好比x.equals(X o),這樣的作法並非覆蓋Object的equals方法,而是重載了Object.equals方法;爲了防止這樣的錯誤,可使用@Override註解。

  十一、推薦使用Google的AutoValue框架,或者IDEA來生成equals方法;

 

第11條、覆蓋equals時總要覆蓋hashCode

  一、若是定義類的時候只是覆蓋了equals方法,可是沒有hashCode方法,那麼類的對象在使用HashMap和HashSet的時候就會出現問題,至於爲何,能夠看一下HashMap的底層原理;

  二、規範中對於equals和hashCode的描述:

  a:應用執行期間,只要調用equals進行比較的那些字段信息沒有發生更改,那麼對同一個對象調用屢次equals方法,hashCode都必須返回同一個值;一個程序與另一個程序的執行過程當中,執行hashCode的返回值能夠不一致;

  b:兩個對象調用equals方法相等,那麼hashCode也必需要相等;若是沒有覆蓋hashCode方法,那麼就沒法知足這一條;

  c:若是經過equals方法比較不相等,可是hashCode是能夠相等的;

三、覆蓋hashCode的時候,應該儘可能作到「爲不相等的對象產生不相等的散列碼」,可是不要爲了提升性能,試圖從散列碼計算中排除某個對象關鍵域;

四、推薦使用Google的AutoValue框架,或者IDEA來生成hashCode方法;

下面是一個hashCode的示例:

@Override
public int hashCode() {
    // 類中有id,name,addr三個屬性,equals中也是比較這3個屬性值,下面是計算hashCode的方式
    int result = id;
    result = 31 * result + (name != null ? name.hashCode() : 0);
    result = 31 * result + (addr != null ? addr.hashCode() : 0);
    
    // 若是增長了一個gender的屬性,那麼就加這麼一行
    // result = 31 * result + (gender!= null ? gender.hashCode() : 0);
    
    return result;
}

  五、若是懶得本身動手覆蓋equals、hashCode方法,可使用lombok的@EqualsAndHashCode註解。

 

第12條:始終要覆蓋toString

  一、不覆蓋toString方法,在打印對象的時候,輸出的是ClassName@Number格式,沒法看出對象的具體信息,調試時麻煩;

  二、靜態工具類、枚舉類不須要覆蓋toString方法,前者是由於沒有意義,後者是由於Java已經提供了支持;

 

第13條、謹慎使用clone

  實現Clonable接口,而後覆蓋clone方法的例子

// 方式1
@Override
protected Object clone() throws CloneNotSupportedException {
    return (Person) super.clone();
}

// 方式2
@Override
public Person clone() {
    try {
        return (Person) super.clone();
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}

  注意事項:

  一、不可變的類不該該提供clone方法;

  二、須要注意深拷貝與淺拷貝的狀況:若是屬性是基本數據類型就不存在這個問題,但若是是引用數據類型(包括String),調用clone方法只是單純的淺拷貝,須要進行遞歸clone;深拷貝與淺拷貝的區別; 

  三、解決深拷貝與淺拷貝,還能夠先調用super.clone,而後把結果對象中的全部域都設置爲初始狀態,而後調用高層方法進行設置對象屬性;

  四、若是編寫線程安全的類須要實現Clonable接口,那麼clone方法也須要改爲同步的,只須要在clone方法前加synchronized關鍵字便可;

  五、對象拷貝的最佳方式提供拷貝構造器和拷貝工廠,以下示例:

package cn.ganlixin.effective_java;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Person {

    int id;
    String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    // 拷貝構造器
    public Person(Person person) {
        this(person.id, person.name);
    }

    // 拷貝工廠,方法名隨意
    public static Person newInstance(Person person) {
        return new Person(person);
    }

    public static void main(String[] args) {
        Person p1 = new Person(1,"hello");

        Person p2 = Person.newInstance(p1);
        System.out.println(p1 == p2);   // false

        p1.setName("world");
        System.out.println(p1.getName());   // world
        System.out.println(p2.getName());   // hello
    }
}

  

第14條:考慮實現Comparable接口

  一、實現Compareable接口後,能夠覆蓋compareTo方法,這個方法能夠進行等同性比較(和equal功能相同),還能夠指定比較的順序(本身寫equals時,也能夠指定,只不過通常會優先比較最有可能不一樣的屬性);

  二、實現了Compareable接口,而且覆蓋compareTo方法後,能夠進行元素的排序操做;

  三、實現Compareable接口時,推薦加上泛型的類型,能夠免去compareTo中強制類型轉換;另外compareTo方法中,數值類型(int、double..)不建議使用>或者<符號,建議使用裝箱基本數據類型

public class Person implements Comparable<Person> {

    int id;

    @Override
    public int compareTo(Person o) {
        // 不推薦使用> 或者 <符號進行比較,好比 return (x < y) ? -1 : ((x == y) ? 0 : 1);
        // 其餘數值類型也是同樣的
        return Integer.compare(id, o.id);
    }
}

  四、比較時,value1 > value2 返回 正數;value1 < value2 返回負數;二者相等,返回0;不要使用兩數相減的方式,由於可能會出現溢出:

@Override
public int compareTo(Person o) {
    // 不推薦使用兩數相減,容易形成整數溢出
    return id - o.id;
}
相關文章
相關標籤/搜索