深入理解hashCode()与equals()

读者人群

       本文章适合有一定的Java基础的人阅读。

问题引入

       如果hashMap的key是一个自定义的类,怎么办?

需要重写自定义类的hashCode()与equals()方法。
下面来分析下具体的原因:
·       下面的例子是用来测试的小demo:

package j2se.equals方法;

/** @version:
 * @Description: JavaBean,User类
 * @author: wangxi
 * @date: 2018年8月12日 上午9:28:57
*/
public class User {
    private String username;

    private int age;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }
    // 重写Object类中的toString()
    @Override
    public String toString() {
        return "User [username=" + username + ", age=" + age + "]";
    }
    // 重写Object类的hashCode()
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((username == null) ? 0 : username.hashCode());
        return result;
    }
    // 重写Object类的equals()
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        User other = (User) obj;
        if (age != other.age)
            return false;
        if (username == null) {
            if (other.username != null)
                return false;
        } else if (!username.equals(other.username))
            return false;
        return true;
    }


}
package j2se.equals方法;

import java.util.HashMap;
import java.util.Map;

/** @version:
 * @Description: 测试对象的equals()与hashCode()
 * @author: wangxi
 * @date: 2018年8月12日 上午9:29:09
*/
public class Test {
    public static void main(String[] args) {
        User user1 = new User("wangx", 1);
        User user2 = new User("wangx", 1);
        // == 一定是比较两个对象在堆内存的地址 
        System.out.println(user1 == user2);
        // 采用equals比较时,只会调用对象的重写的equals方法,否则调用Object类的equals(),不会调用hashCode()
        System.out.println(user1.equals(user2));

        HashMap<User, Integer> map = new HashMap<>();
        // put()会先调用hashCode().进行O(1)时间判断要加入的元素是否在容器中,这样节省了时间
        map.put(user1, 1);
        map.put(user2, 1);
        System.out.println(map);
    }
}
// 输出结果如下:
false
true
{User [username=wangx, age=1]=1}  // 此时重写了那两个方法的输出。

案例分析:Map的key使用了自定义类,并且User类也重写了上述两个方法,此时在Test类的map输出只有一个元素。这与map的put()有关系:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 初始化tab、n变量
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 判断key的hash值对应的Entry数组头元素是否为空
        if ((p = tab[i = (n - 1) & hash]) == null)
            // 如果为空,直接添加
            tab[i] = newNode(hash, key, value, null);
        // 如果不为空,会覆盖掉原来的值(前提是调用equals方法存在这个值)
        else {
        .........

而key的hash值怎么来的呢,请看下面代码:

static final int hash(Object key) {
        int h;
        /** 下面代码调用了key的hashCode()方法,这也不难理解为什么
         *  map的key为自定义类时需要重写上述两个方法。
         */
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

还需要明确一点:putVal()方法会先调用hashCode()方法,进行O(1)时间比较,如果两个HashCode相等,进而会调用equals()方法比较。

假设User类没有重写上述两个方法:
那么putVal()会直接调用Object类的hashCode()与equals()方法。下面看看Object类的源码:

 // 调用底层的方法,生成32位机器码    
 public native int hashCode();
// 直接比较对象在堆内存的地址
public boolean equals(Object obj) {
        return (this == obj);
    }

如果User类没有重写上述方法,那么最终输出的map对象就会有两个值相同的User对象。(new出来的对象,地址一定不相同,hashCode也不相同。)

HashSet的应用

其实原理和hashMap基本相同
且看hashSet源码:

/** add()方法最终会调用map中的put()方法
*   而此时map的key为add()方法传进来的参数,value是常量PRESENT
*/
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
}
// 下面是JDK对常量的解释
// Dummy value to associate with an Object in the backing Map
 private static final Object PRESENT = new Object();

Set集合不含有重复元素也利用hashCode与equals(),并且还利用了hashCode()方法O(1)时间判断元素是否在集合中的特性,大大的节省了时间。

public class SetDemo {
    // 验证set集合元素的唯一性
    public static void main(String[] args) {
        Set<Integer> set1 = new HashSet<>();
        set1.add(1);
        set1.add(1);
        System.out.println(set1);

        Set<Integer> set2 = new TreeSet<>();
        set2.add(1);
        set2.add(1);
        System.out.println(set2);
    }
}
// 输出结果:
[1]
[1]

在String类的应用

且来看下String类的源码:

// 把字符串中每个字符进行累加,就得到字符串的hashcode值
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;
    }
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) {
                // 只要有一个字符不相等,就返回false,两个字符串不等。
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

hashMap的key如果是String类型时,此时会缓存key的hashcode值,这也是一种JDK优化策略。
effective Java一书中说道:hashCode()与equals()同存在,同不存在。

猜你喜欢

转载自blog.csdn.net/qq_36387730/article/details/81604810