咱們在覆蓋equals方法時,必須遵照它的通用約定:java
1.自反性。對於任何非null的引用值x,x.equals(x)必須返回true;緩存
2.對稱性。對於任何非null的引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true;ide
一個「不區分大小寫」字符串的例子:測試
public class CaseInsensitiveString { private final String s; public CaseInsensitiveString(String s){ if(s == null){ throw new NullPointerException(); } this.s = s; } @Override public boolean equals(Object obj) { if(obj instanceof CaseInsensitiveString){ return s.equalsIgnoreCase(((CaseInsensitiveString)obj).s); } if(obj instanceof String){ return s.equalsIgnoreCase((String)obj); } return false; } public static void main(String[] args){ CaseInsensitiveString s1 = new CaseInsensitiveString("xxx"); String s2 = "xxx"; System.out.println(s1.equals(s2)); //true System.out.println(s2.equals(s1)); //false } }
該例子明顯違反了對稱性。優化
3.傳遞性。對於任何非null的引用值x,y和z。若是x.equals(y)返回true,而且y.equals(z)返回true,那麼x.equas(z)也必須返回true。this
一個違反對稱性的例子:spa
public class Point { private final int x; private final int y; public Point(int x, int y){ this.x = x; this.y = y; } @Override public boolean equals(Object obj) { if(!(obj instanceof Point)){ return false; } Point p = (Point)obj; return p.x == x && p.y == y; } @Override public int hashCode() { int result = 17; result = 31 * result + x; result = 31 * result + y; return result; } }
public class ColorPoint extends Point{ private final Color color; public ColorPoint(int x, int y, Color color){ super(x, y); this.color = color; } @Override public boolean equals(Object obj) { if(!(obj instanceof Point)){ return false; } //if obj is a normal Point, do a color-blind comparison if(!(obj instanceof ColorPoint)){ return obj.equals(this); } return super.equals(obj) && ((ColorPoint)obj).color == color; } public static void main(String[] args){ ColorPoint p1 = new ColorPoint(1,3, Color.RED); Point p2 = new Point(1,3); ColorPoint p3 = new ColorPoint(1,3, Color.BLUE); System.out.println(p1.equals(p2)); //true System.out.println(p2.equals(p3)); //true System.out.println(p1.equals(p3)); //false } } enum Color{ RED, GREEN, BLUE; }
上面的例子提供了對稱性,但卻犧牲了傳遞性。咱們沒法在擴展可實例化類的同時,既增長新的值組件,同時又保留equals約定,除非願意放棄面向對象的抽象所帶來的優點。3d
4.一致性。對於任何非null的引用值x和y,只要equals的比較操做在對象中所用到的信息沒有被改變,屢次調用x.equas(y)就會一致地返回true或false;code
5.非空性。對於任何非null的引用值x,x.equas(null)一定會返回false。orm
綜上,實現高質量equals方法的訣竅:
1.使用==操做符檢查「參數是否爲這個對象的引用」.
2.使用instanceof操做符檢查「參數是否爲正確的類型」。
3.把參數轉換爲正確的類型。
4.對於該類中的每一個「關鍵字」域,檢查參數中的域是否與該對象中的域相匹配。
String類中的equals方法:
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String) anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
首先明確一個概念,兩個對象使用equals返回true,則它們的hashCode也必定相等;若是兩個對象的hashCode相等,則它們的equals則不必定相等。
考慮一個簡單的PhoneNumber類:
public class PhoneNumber { private final short areaCode; private final short prefix; private final short lineNumber; public PhoneNumber(int areaCode,int prefix, int lineNumber){ rangeCheck(areaCode, 999, "area code"); rangeCheck(prefix, 999, "prefix"); rangeCheck(lineNumber, 9999, "lineNumber"); this.areaCode = (short) areaCode; this.prefix = (short) prefix; this.lineNumber = (short) lineNumber; } private static void rangeCheck(int arg, int max, String name){ if(arg < 0 || arg > max){ throw new IllegalArgumentException(name + " : " + arg); } } @Override public boolean equals(Object obj) { if(obj == this){ return true; } if(!(obj instanceof PhoneNumber)){ return false; } PhoneNumber pn = (PhoneNumber) obj; return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode; } public static void main(String[] args){ Map<PhoneNumber, String> m = new HashMap<>(); m.put(new PhoneNumber(707, 867, 5039), "Kevin"); String name = m.get(new PhoneNumber(707, 867, 5039)); //若是PhoneNumber類不實現hashCode方法,則返回null System.out.println(name); } }
main方法測試返回爲null,這是由於該類沒有實現hashCode方法,致使兩個相等的實例具備不一樣的散列碼。put方法把電話號碼對象存放在一個散列通中,而get方法卻在另一個桶中查找這個電話號碼。即便兩個對象正好被放在一個桶中,get方法也一定會返回爲null,由於hashMap有項優化,能夠把每一個項有關的散列碼緩存起來,若是散列碼不匹配,也沒必要檢驗對象的等同性。因此須要提供一個hashCode方法:
@Override public int hashCode() { int result = 17; result = 31 * result + areaCode; result = 31 * result + prefix; result = 31 * result + lineNumber; return result; }
再次用main方法測試,返回「Kevin」。
雖然java.lang.Object提供了一個toString方法,但返回的字符串一般不是用戶但願的信息。應該返回一個「簡潔的,信息豐富,而且易於閱讀的表達形式」。在實際應用中,toString方法應該返回對象中包含的全部值得關注的信息譬如以前電話號碼的例子:
@Override public String toString() { return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber); }
Cloneable並無包含任何方法,那到底有什麼做用呢?它決定了Object中受保護clone方法的實現行爲,若是一個類實現了cloneable,Object的clone方法就返回該對象中的逐域拷貝,不然就會拋出cloneNotSupportedException異常。clone帶來的問題不少,因此能夠不用clone方法儘可能不用。clone方法也分淺複製和深複製,這裏分別舉點例子。
淺複製:
public class Stack implements Cloneable{ private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack(){ elements = new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(Object e){ elements[size++] = e; } public Object pop(){ if(size == 0){ throw new EmptyStackException(); } Object result = elements[--size]; elements[size] = null; //清空過時引用,否則會致使內存泄漏 return result; } private void ensureCapacity(){ if(elements.length == size){ elements = Arrays.copyOf(elements, 2 * size + 1); } } @Override protected Stack clone() throws CloneNotSupportedException { // TODO Auto-generated method stub Stack result = (Stack) super.clone(); // result.elements = elements.clone(); return result; } public static void main(String[] args) throws CloneNotSupportedException{ Stack s = new Stack(); PhoneNumber pn = new PhoneNumber(111, 222, 3333); s.push(pn); Stack s2 = (Stack) s.clone(); System.out.println(s.pop()); // (111) 222-3333 System.out.println(s2.pop()); // null 淺複製,s和s2擁有相同的elements引用,s的pop方法清空了過時引用,因此s2的pop方法返回null } }
深複製:
public class HashTable { private Entry[] buckets; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; private static class Entry{ final Object key; Object value; Entry next; Entry(Object key, Object value, Entry next){ this.key = key; this.value = value; this.next = next; } //recursively copy the linked list headed by this Entry //遞歸方法針對列表中的每一個元素,它都要消耗一段內存空間,若是鏈表比較長,則容易致使棧溢出 //能夠用下面的迭代方式進行對象的深複製 // Entry deepCopy(){ // return new Entry(key, value, next == null ? null : next.deepCopy()); // } //iteratively copy the linked list headed by this Entry 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; } } public HashTable(){ buckets = new Entry[DEFAULT_INITIAL_CAPACITY]; } //Broken - results in shared internal state! /*@Override protected HashTable clone() throws CloneNotSupportedException { HashTable result = (HashTable) super.clone(); result.buckets = buckets.clone(); return result; }*/ @Override protected HashTable clone() throws CloneNotSupportedException { HashTable result = (HashTable) super.clone(); result.buckets = new Entry[buckets.length]; for(int i = 0; i < buckets.length; i++){ if(buckets[i] != null){ result.buckets[i] = buckets[i].deepCopy(); } } return result; } }
關於Comparable接口其中只有一個方法——compareTo。此方法和equals有相似之處,不過它所表達的含義相比equals要更多。equals一般是比較兩個值是否相等,相等返回true,不相等返回false。compareTo則約定爲第1對象若「大於」第2個對象則返回整數,「等於」則返回0,「小於」則返回負數,compareTo能約定更爲複雜的「比較」,例如比較兩個字符串進行字典序的比較,str = 「abc」, str2 = 「abd」,str.equals(str2)返回false,而str.compareTo(str2)則返回正數。compareTo與equals同樣一樣須要遵照自反性、對稱性、傳遞性。一樣有一個強烈的建議就是compareTo應該返回和equals方法相同的結果,但若是不一致,也不是不能夠,就是最好能在註釋中寫明兩個方法返回的結果不一樣。
CompareTo方法中域的比較是順序的比較,而不是等同性的比較,比較對象引用域能夠遞歸調用compareTo方法,若是一個域沒有實現Comparable接口,或者你須要一個非標準的排序方式,就能夠用一個顯示的comparator來代替。或者編寫本身的comparator,或者使用已有的comparator。
public class CaseInsensitiveString2 implements Comparable<CaseInsensitiveString2>{ private final String s; public CaseInsensitiveString2(String s){ if(s == null){ throw new NullPointerException(); } this.s = s; } @Override public boolean equals(Object obj) { if(obj instanceof CaseInsensitiveString2){ return s.equalsIgnoreCase(((CaseInsensitiveString2)obj).s); } if(obj instanceof String){ return s.equalsIgnoreCase((String)obj); } return false; } public static void main(String[] args){ CaseInsensitiveString2 s1 = new CaseInsensitiveString2("xxx"); String s2 = "xxx"; System.out.println(s1.equals(s2)); //true System.out.println(s2.equals(s1)); //false } @Override public int compareTo(CaseInsensitiveString2 o) { // TODO Auto-generated method stub return String.CASE_INSENSITIVE_ORDER.compare(s, o.s); } }