详解hashcode和equals

1. equals 和 == 的区别,hashcode 是什么

java.lang.Object 类中提供了一些基本的方法

/**
 * Returns a hash code value for the object. This method is
 * supported for the benefit of hash tables such as those provided by
 * {@link java.util.HashMap}.
 * <p>
 * The general contract of {@code hashCode} is:
 * <ul>
 * <li>Whenever it is invoked on the same object more than once during
 *     an execution of a Java application, the {@code hashCode} method
 *     must consistently return the same integer, provided no information
 *     used in {@code equals} comparisons on the object is modified.
 *     This integer need not remain consistent from one execution of an
 *     application to another execution of the same application.
 * <li>If two objects are equal according to the {@code equals(Object)}
 *     method, then calling the {@code hashCode} method on each of
 *     the two objects must produce the same integer result.
 * <li>It is <em>not</em> required that if two objects are unequal
 *     according to the {@link java.lang.Object#equals(java.lang.Object)}
 *     method, then calling the {@code hashCode} method on each of the
 *     two objects must produce distinct integer results.  However, the
 *     programmer should be aware that producing distinct integer results
 *     for unequal objects may improve the performance of hash tables.
 * </ul>
 * <p>
 * As much as is reasonably practical, the hashCode method defined by
 * class {@code Object} does return distinct integers for distinct
 * objects. (This is typically implemented by converting the internal
 * address of the object into an integer, but this implementation
 * technique is not required by the
 * Java&trade; programming language.)
 *
 * @return  a hash code value for this object.
 * @see     java.lang.Object#equals(java.lang.Object)
 * @see     java.lang.System#identityHashCode
 */
public native int hashCode();
/**
 * Indicates whether some other object is "equal to" this one.
 * <p>
 * The {@code equals} method implements an equivalence relation
 * on non-null object references:
 * <ul>
 * <li>It is <i>reflexive</i>: for any non-null reference value
 *     {@code x}, {@code x.equals(x)} should return
 *     {@code true}.
 * <li>It is <i>symmetric</i>: for any non-null reference values
 *     {@code x} and {@code y}, {@code x.equals(y)}
 *     should return {@code true} if and only if
 *     {@code y.equals(x)} returns {@code true}.
 * <li>It is <i>transitive</i>: for any non-null reference values
 *     {@code x}, {@code y}, and {@code z}, if
 *     {@code x.equals(y)} returns {@code true} and
 *     {@code y.equals(z)} returns {@code true}, then
 *     {@code x.equals(z)} should return {@code true}.
 * <li>It is <i>consistent</i>: for any non-null reference values
 *     {@code x} and {@code y}, multiple invocations of
 *     {@code x.equals(y)} consistently return {@code true}
 *     or consistently return {@code false}, provided no
 *     information used in {@code equals} comparisons on the
 *     objects is modified.
 * <li>For any non-null reference value {@code x},
 *     {@code x.equals(null)} should return {@code false}.
 * </ul>
 * <p>
 * The {@code equals} method for class {@code Object} implements
 * the most discriminating possible equivalence relation on objects;
 * that is, for any non-null reference values {@code x} and
 * {@code y}, this method returns {@code true} if and only
 * if {@code x} and {@code y} refer to the same object
 * ({@code x == y} has the value {@code true}).
 * <p>
 * Note that it is generally necessary to override the {@code hashCode}
 * method whenever this method is overridden, so as to maintain the
 * general contract for the {@code hashCode} method, which states
 * that equal objects must have equal hash codes.
 *
 * @param   obj   the reference object with which to compare.
 * @return  {@code true} if this object is the same as the obj
 *          argument; {@code false} otherwise.
 * @see     #hashCode()
 * @see     java.util.HashMap
 */
public boolean equals(Object obj) {
    return (this == obj);
}

hashcode 和 equals方法都不是final类型的,因此可以被继承类重写,在Object中 equals方法 最终也是通过 == 来比较的,这里equals只能比较两个对象的内存地址,如果想比较两个对象的其他信息是否相等,就要重写equals方法。另外我们从这个方法的注释来看,jdk规定了实现该方法应该遵循的规定:

(1)自反性:x.equals(x)必须返回true。
(2)对称性:x.equals(y)与y.equals(x)的返回值必须相等。
(3)传递性:x.equals(y)为true,y.equals(z)也为true,那么x.equals(z)必须为true。
(4)一致性:如果对象x和y在equals()中使用的信息都没有改变,那么x.equals(y)值始终不变。

(5)非null:x不是null,y为null,则x.equals(y)必须为false。

hashcode是什么:

java.lang.Object 中的hashcode 是将对象在内存中的地址作为哈希码返回,jvm堆内存中不同位置的对象的哈希码一定会不同,其实hashcode真正使用到的地方是散列表对象,例如HashMap, HashTable, HashSet,hashcode获取对象的散列码,来确定当前对象在散列表中的位置

和equals一样,重写hashcode也要遵循相关的规定:

  (1) 如果两个对象通过equals方法比较,返回结果是true相等,那么他们的hashcode值一定相等

  (2) 如果两个对象通过equals方法比较,返回结果是false不相等,那么他们的hashcode值可以不相等,也可以相等

  (3) 如果两个对象的hashcode值相等,那么通过equals比较的结果不一定相等

  (4) 如果两个对象的hashcode值不相等,那么通过equals比较的结果一定不相等

2. 为什么 重写equals 方法后,一定要重写hashcode方法

首先看一段代码:    

public class jdkTest {

    public static void main(String[] args) {
        // 新建Person对象,
        User p1 = new User("啊啊啊", 26);
        User p2 = new User("啊啊啊", 26);
        User p3 = new User("发发发", 24);

        // 新建HashSet对象
        HashSet set = new HashSet();
        set.add(p1);
        set.add(p2);
        set.add(p3);

        // 比较p1  p2, 并打印它们的hashCode()
        System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
        // 打印set
        System.out.printf("set:%s\n", set);
    }

    
    private static class User {
        int age;
        String name;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
        }

        /**
         * @desc 覆盖equals方法
         */
        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }

            if (this == obj) {
                return true;
            }

            //判断是否类型相同
            if (this.getClass() != obj.getClass()) {
                return false;
            }

            User person = (User)obj;
            return name.equals(person.name) && age == person.age;
        }
    }
}

看一下执行结果:


从上图中可以看出,虽然p1.equals(p2) 的结果为true,即p1对象和p2对象相等,他们的hashcode不相等,set中会存在两条重复的对象数据,这和我们所了解的java.util.Set中 存放的数据一定不重复 产生冲突了。想要知道为什么此时set中会存在重复数据,就必须先了解一下HashSet中add方法的原理:


由此可见,HashSet中add方法最终调用的是HashMap的put方法,再研究一下HashMap的put方法:



由jdk中最终的putval方法可知,在比较两个Node<K,V>是否相等的时候,首先会比较两者的hashcode是否相等,如果两者的hashcode不相等,那么就会默认这两个Node<K,V>不相等,所以在HashSet中放入了两个重复的对象,所以在重写hashcode和equals的时候一定要遵循上面所介绍的几个原则,不然会导致意外情况发生。

3. 既然equals能够比较两个对象,为什么还需要hashcode 

我们知道,通过重写equals方法可以很好的用来比较两个对象,但是这种比较的效率是比较低的,如果我们在比较的过程中,首先使用hashcode来比较,如果hashcode都不一样,那么两个对象肯定不相等,如果hashcode相等,然后再使用equals来比较,如果equals也相等,那么两个对象肯定相等,这样就能大大的提高比较的效率和比较的正确性。

4. 如何重写 equals 和 hashcode

hashcode重写:

《Effective Java》中提出了一种简单通用的hashCode算法:
A、初始化一个整形变量,为此变量赋予一个非零的常数值,比如int result = 17;
B、选取equals方法中用于比较的所有域(之所以只选择equals()中使用的域,是为了保证上述原则的第1条),然后针对每个域的属性进行计算:
(1) 如果是boolean值,则计算f ? 1:0
(2) 如果是byte\char\short\int,则计算(int)f
(3) 如果是long值,则计算(int)(f ^ (f >>> 32))
(4) 如果是float值,则计算Float.floatToIntBits(f)
(5) 如果是double值,则计算Double.doubleToLongBits(f),然后返回的结果是long,再用规则(3)去处理long,得到int
(6) 如果是对象应用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取递归调用hashCode的方式。否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode 值为0
(7) 如果是数组,那么需要为每个元素当做单独的域来处理。java.util.Arrays.hashCode方法包含了8种基本类型数组和引用数组的hashCode计算,算法同上。

C、最后,把每个域的散列码合并到对象的哈希码中。

private static class User {
    int age;
    String name;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
            "age=" + age +
            ", name='" + name + '\'' +
            '}';
    }

    /**
     * @desc 覆盖equals方法
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        if (this == obj) {
            return true;
        }

        //判断是否类型相同
        if (this.getClass() != obj.getClass()) {
            return false;
        }

        User person = (User)obj;
        return name.equals(person.name) && age == person.age;
    }

    @Override
    public int hashCode() {
        int hash = 17;
        hash = hash * 31 + name.hashCode();
        hash = hash * 31 + age;
        return hash;
    }
}

猜你喜欢

转载自blog.csdn.net/T2080305/article/details/80899746
今日推荐