一、使用==來比較兩個對象的時候,比較的是兩個對象在內存中的地址是否相同(兩個引用指向的是否爲同一個對象);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方法;
一、若是定義類的時候只是覆蓋了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註解。
一、不覆蓋toString方法,在打印對象的時候,輸出的是ClassName@Number格式,沒法看出對象的具體信息,調試時麻煩;
二、靜態工具類、枚舉類不須要覆蓋toString方法,前者是由於沒有意義,後者是由於Java已經提供了支持;
實現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 } }
一、實現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; }