java判斷兩個對象是否相等、以及hashCode和equals方法、Objects類


使用 == 和 equals
== :判斷兩個對象的地址是否相等。比較其內存地址
equlas:作用是比較兩個對象是否相等,存在兩種情況
  情況1:類沒有覆蓋重寫equals方法,則使用的是父類 Object 的 equals 方法。即通過 “==” 比較兩個對象內存地址。
  情況2:如果覆蓋重寫了equals方法,一般,比較兩個對象的內容是否相等。

比如在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;
    }
String中hashCode()


關於自定義類中比較對象對象是否相等的實現:
--- 首先先了解什么是hashcode,作用 ---
  hashcode就是散列碼,使用高效率的哈希算法來定位查找對象。
比如 String str = "abcd",那么計算機會先計算器散列碼,再將其放入到對應的內存數組索引處。
在Object類中的 toString 方法:

return getClass().getName() + “@” + Integer.toHexString(hashCode());  

在String類中的hashCode算法

public int hashCode() {
    int h = hash;  //private int hash; // Default to 0
    if (h == 0 && value.length > 0) {
        char val[] = value;  //value是存儲的字符數組

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

數學表達式:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]  

  s[ i ] 即String 的第 i 個字符,n 是 str 的長度。
上述表達式的計算過程:

String str = "abcd";
 
h = 0
value.length = 4
 
val[0] = a
val[1] = b
val[2] = c
val[3] = d
 
h = 31*0 + a
  = a
 
h = 31 * (31*0 + a) + b
  = 31 * a + b
 
h = 31 * (31 * (31*0 + a) + b) + c
  = 31 * (31 * a + b) + c
  = 31 * 31 * a + 31 * b + c
 
h = 31 * (31 * 31 * a + 31 * b + c) + d
  = 31 * 31 * 31 * a + 31 * 31 * b + 31 * c + d
 
h = 31 ^ (n-1) * val[0] + 31 ^ (n-2) * val[1] + 31 ^ (n-3) * val[2] + ...+ val[n-1]

  -->雖然 字符串長度太長,int類型可能丟失數據,但是依然可以起到散列的作用。

為什么使用 31 作為散列碼,而不是其他的數字?
計算機的乘法會涉及到移位計算,選擇 31 的原因是一個素數。

質數又稱素數。指在一個大於1的自然數中,除了1和此整數自身外,沒法被其他自然數整除的數

在存儲數據,計算hash地址的時候,我們希望能盡量較少同樣的 hash 地址,即“hash沖突”。
如果得到的相同的 hash 地址過多的話,那么這些數據組成的hash鏈(鏈表)就會更長,從而會降低查詢的效率。
所以在選擇系數的時候選擇盡量長的(31=11111 [2] )系數且乘法盡量不要溢出(大於31 的話,很容易溢出)的系數。
如果計算出來的Hash 地址越大,那么沖突就會越小,查找的效率也會提高。

31 可以由 i*31 == (i<<5)-i 表示,使用31 是為了更好地分配hash (int類型) 地址。並且31 只占用了 5 bits。
Integer 類的最大值是  : 2147483647
在java乘法中如果數字相乘過大,會導致移除的問題,導致數據的丟失。

--- Objects 類中的 hash() 方法實現 ---

public static int hash(Object... values) {
    return Arrays.hashCode(values);
}
    |
    | Arrays類的hashCode() 方法
    V
public static int hashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}
    |
    | Object的hashCode() 方法
    V
public native int hashCode();

  -->Object 類中的 hashCode 方法,是本地方法,也就是C語言實現。該方法將對象的內存地址轉換成 int 類型整數返回。

--- 關於hashCode 和equals 方法的重寫 ---

public class Person {    
    private String name;
    private int age;
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

 

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
    |
    |
    V
 public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
    |
    |
    V
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
    |
    |
    V
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;
        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;
                }
                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;
}
HashSet保證唯一無序

--- 為什么要有hashCode ---
在將對象加入到HashSet 的時候,在HashMap 中會先計算對象的 hashcode 值來判斷對象加入的位置。
如果沒有相等的 hashcode 則表示該對象沒有添加過。進行添加。
如果存在星等的 hashcode(由於哈希碰撞),則使用 equals 方法檢查兩個對象是否相同的。
  如果相同的話就不會進行加入。
  如果相同,就會重新散列到其他位置。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM