[TOC]
## 覆請equals遵守規則
Object中equals方法的實現僅僅是比較了兩個對象的地址,對于某些類來說正是所需用的、毋需復寫的
equals方法的復寫需要滿足以下通用約定
* 自反性:對于任何非空引用值 x,x.equals(x) 都應返回 true,就是自己和自己比較必須相等。
* 對稱性:對于任何非空引用值 x 和 y,當且僅當 y.equals(x) 返回 true 時,x.equals(y) 才應返回 true,就是x若等于y,那么y也應該等于x。
* 傳遞遞性:對于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 應返回 true。
* 一致性:對于任何非空引用值 x 和 y,多次調用 x.equals(y) 始終返回 true 或始終返回 false,前提是對象上 equals 比較中所用的信息沒有被修改。
* 非空性:對于任何非空引用值 x,x.equals(null) 都應返回 false。
如無必要不要復寫equals 方法,如果復寫了此方法一定要記得復寫hashCode方法,因為兩個對象相等,它們的hashCode也要相等,下面是equals方法的常用步驟
~~~
@Override
public boolean equals(Object o) {
//判斷引用是否相等
if (o == this) {
return true;
}
//判斷參數類型是否正確 如果o為null也會返回false
//這里判斷的是class類型,也有可能是接口類型,這樣就允許實現這個接口的類之間進行比較
//AbstractSet,AbstractList,AbstracMap的equals方法這一步都是比較的接口
if (!(o instanceof PhoneNumber)) {
return false;
}
//類型轉換
// AbstractSet的類型轉換 Collection<?> c = (Collection<?>) o;
PhoneNumber pNum = (PhoneNumber) o;
// 判斷重要字段的相等,如果使用的是接口,調用接口的方法獲取字段
// 對于基本類型 如果不是float或double 直接使用==比較
// float使用Float.compare(float, float), 原因參考testFloat方法
//double使用Double.compare(double, double) 同上
//Float.equals和Double的equals都設計autobox,影響性能
//引用類型繼續調用其equals方法
// 上述方法也同樣適用于數組元素,如果要比較整個數值,使用Arrays.equals對應的方法
//對象的某些字段能為Null,為了避免NPE,使用Objects.equals(Object, Object)
return this.linNum == pNum.linNum &&this.areaCode == pNum.areaCode &&this.prefix == pNum.this.prefix;
~~~
## 覆蓋equals總是覆蓋hashCode
上文說到如果復寫equals方法一定要復寫hashCode方法。下面說說hash值的計算
確保與equals中使用的字段一致
* 如果字段是基本類型,使用包裝類計算hash值如Float.hashCode(f)
* 如果字段是引用類型,并且在equals方法中遞歸調用去equals方法,那么這里也遞歸調用其hashCode方法
* 如果字段是數組類型,對其中重要元素的hash計算上述方法同樣使用,如果要計算整個數組的hash值,使用Arrays.hashCode(array)
* 質素31的選取是個傳統,能盡量讓不同對象擁有不同hash值,即分布均勻,
* Objects.hash(linNum,prefix,areaCode)方法簡便,但涉及可變數組的創建和拆裝箱操作,性能敏感
此方法返回的值不應該有詳細規范,如String的hashCode方法返回精確值就是一個失誤
## ==和equals和hashCode
基本數據類型:“==”比較的是其值
類:“==”比較數據內存地址是否相同
“equals”是Object方法,在子類未重寫方法時
~~~
public boolean equals(Object obj) {
return (this == obj);
}
~~~
hashCode返回的是對象在內存中地址轉換成的一個int值,所以如果沒有重寫 hashCode()方法,任何對象的hashCode()方法都是不相等的。
## 始終要覆蓋toString
盡量復寫toString方法,雖然不及equals和hashCode方法必要,但良好的類描述將能提供充分和友好的信息。
## 謹慎覆蓋clone
那么在java語言中,有幾種方式可以創建對象呢??
* 使用new操作符創建一個對象。
* 使用clone方法復制一個對象。
如果一個class 實現了Cloneable接口 那么它應該 提供一個public clone方法
* 這是一個毋需構造器就能創建對象的方法
* 注意:這種方式復制對象容易出錯而且復雜,難以維護 僅僅在對基本類型數組的復制是可取的
* 這個方法是個淺拷貝,也就是字段到字段的復制,如果都是基本類型,那將是一步到位的,
* 但如果還有引用類型,它們指向的對象不會被拷貝,而僅僅拷貝了引用,這就會導致拷貝后的對象和被拷貝的對象不是相互獨立的,這些引用指向了相同的對象,也就是任何一方的修改都在另一方得到體現
* 如果要深度拷貝,可以每個引用類型都需要實現cloneable接口和clone方法
* 在實際中要實現對象拷貝,并不建議使用clone方法,而建議采用靜態工廠或構造器方式提供復制操作
## comparable
* 如果此方法返回0那么equals應該返回true,如果不是一定要說明不一致性
* HashSet依賴equals比較元素是否重復,TreeSet依賴compareTo給元素排序
* 不要使用來比較大小,對浮點有例外(基本類型的OK的),也不要使用減號,會有溢出
## 推薦閱讀
[深入理解 Java Object](https://www.jianshu.com/p/4222abe52c12)
《Effective Java》第二章
- Java
- Object
- 內部類
- 異常
- 注解
- 反射
- 靜態代理與動態代理
- 泛型
- 繼承
- JVM
- ClassLoader
- String
- 數據結構
- Java集合類
- ArrayList
- LinkedList
- HashSet
- TreeSet
- HashMap
- TreeMap
- HashTable
- 并發集合類
- Collections
- CopyOnWriteArrayList
- ConcurrentHashMap
- Android集合類
- SparseArray
- ArrayMap
- 算法
- 排序
- 常用算法
- LeetCode
- 二叉樹遍歷
- 劍指
- 數據結構、算法和數據操作
- 高質量的代碼
- 解決問題的思路
- 優化時間和空間效率
- 面試中的各項能力
- 算法心得
- 并發
- Thread
- 鎖
- java內存模型
- CAS
- 原子類Atomic
- volatile
- synchronized
- Object.wait-notify
- Lock
- Lock之AQS
- Lock子類
- 鎖小結
- 堵塞隊列
- 生產者消費者模型
- 線程池