1.Object對象有哪些方法?
下面,總結一下hashCode()
方法和equals()
方法。
2.hashCode方法
2.1.什麼是hashCode?
- 1、hashCode(散列碼)是JDK根據對象的地址或者字符串或者數字算出來的int類型的數值,也就是哈希碼,哈希碼是沒有規律的,它是一種算法,讓同一個類的對象按照自己不同的特徵儘量的有不同的哈希碼,但不表示不同的對象哈希碼完全不同。在Java中,哈希碼代表對象的特徵。
- 2、散列碼可以是任意的整數,包括正數和複數。兩個相等的對象要球返回相等的散列碼。
- 3、equals與hashCode的定義必須一致:如果
x.equals(y)
返回true,那麼x.hashCode()==y.hashCode()
。
2.2.不同的對象有不同的哈希碼算法
以Object、Integer、String爲例:
- 1、Object類,hashCode返回對象的內存地址經過處理後的結構,由於每個對象的內存地址都不一樣,所以哈希碼也不一樣。
public native int hashCode();
- 2、String類,hashCode根據String類包含的字符串的內容,根據一種特殊算法返回哈希碼,只要字符串所在的堆空間相同,返回的哈希碼也相同。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
//s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
- 3、Integer類,返回的哈希碼就是Integer對象裏所包含的那個整數的數值。
public int hashCode() {
return Integer.hashCode(value);
}
public static int hashCode(int value) {
return value;
}
2.2.hashcode在Java中的相關引用
對於哈希碼本身來說就是一種爲了提高查找效率的一種算法,因此hashcode適合用於絕大多數的容器,例如:HashSet
、HashMap
以及HashTable
。
舉例:
當我們向集合中插入一個對象(不允許重複),如果該對象已經存在的情況下不允許插入,在數據量大的情況下調用equals()方法就造成 了效率緩慢問題。使用hashcode(),會先算出新增對象的哈希值。如果沒有相等的哈希值在該集合中那麼就新增,如果存在相應的哈希值,則調用equals()方法判斷是否相等。
下面是hashMap(JDK1.8)的源碼:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// 計算key的散列碼
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 判斷是否存在相同的key:判斷key的hashCode是否相等,或者通過equals方法判斷
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 判斷是否存在相同的key:判斷key的hashCode是否相等,或者通過equals方法判斷
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
3.equals方法
3.1.equals方法
object類中定義如下:
public boolean equals(Object obj) {
return (this == obj);
}
在Object這個類裏面提供的equals()
方法默認的實現是比較當前對象的引用和你要比較的那個引用它們指向的是否是同一個對象,例如比較流行的面試問題equals()
和==的區別,在object中兩者是相等的都是對一個對象的指向進行比較。
但是,在String中對其進行了重寫:
public boolean equals(Object anObject) {
// 對象的指向比較,理解爲兩個對象的內存地址是否一樣
if (this == anObject) {
return true;
}
// 判斷地址不一樣,還爲什麼要這樣重寫,我想是因爲“String對象和字符串常量的關係”問題,這樣就變成對值的比較了。
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方法,在重寫這個方法時,需要按照以下幾個規則設計:
- 1、自反性:對任意引用值x,
x.equals(x)
的返回值一定爲true; - 2、對稱性:對於任何引用值x,y,當且僅當
y.equals(x)
返回值爲true時,x.equals(y)
的返回值一定爲true; - 3、傳遞性:如果
x.equals(y)=true
,y.equals(z)=true
,則x.equals(z)=true
; - 4、一致性:如果參與比較的對象沒任何改變,則對象比較的結果也不應該有任何改變;
- 5、非空性:任何非空的引用值x,
x.equals(null)
的返回值一定爲false。
3.2.String對象和字符串常量的關係[2]
3.3.爲什麼重寫equals方法,需要重寫hashCode方法?
4.參考資料
[1] https://blog.csdn.net/qq_21163061/article/details/73606523
[2]https://blog.csdn.net/qq_42453117/article/details/97128658
[3]http://www.imooc.com/article/280580?block_id=tuijian_wz