Java的hashCode方法和equals方法

Java的hashCode方法和equals方法

简述

public native int hashCode()

public boolean equals(Object obj) {
    return (this == obj);
}

特性说明

  • 返回对象的哈希值。
  • 支持此方法是为了使用哈希表,例如java.util.HashMap提供的哈希表。

hashCode的通用约定如下:

  • 在 Java 应用程序的一次执行过程中,如果对象的equals比较方法中的信息未被修改,只要对同一对象多次调用 hashCode 方法,该方法就必须始终如一地返回相同的整数。该整数不必在应用程序的一次执行与另一次执行之间保持一致。
  • 如果根据 equals(Object)方法判断两个对象相等,那么在两个对象上分别调用 hashCode 方法必须产生相同的整数结果。
  • 根据equals(Object)方法,如果两个对象不相等,则不要求对这两个对象中的每一个调用hashCode方法必须产生不同的整数结果。然而,程序员应该意识到,为不相等的对象生成不同的整数结果可以提高哈希表的性能。

在合理可行的范围内,类 Object 定义的hashCode方法会为不同的对象返回不同的整数。(这通常是通过将对象的内部地址转换为整数来实现的,但 Java™ 编程语言并不要求这种实现技术)。

特性说明

判断两个对象指向的内存空间的值是否相等,简单说就是比较的是两个对象的内容是否相等

equals 方法实现了非空对象引用的等价关系:

  • 具有自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true
  • 具有对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x)返回 true 时,x.equals(y) 应返回 true
  • 具有传递性:对于任何非空引用值 x、y 和 z,如果x.equals(y)返回 true 且 y.equals(z) 返回 true,则 x.equals(z)应返回 true
  • 具有一致性:对于任何非空引用值 x 和 y,在对象的等价比较中使用的信息没有被修改的情况下,多次调用 x.equals(y) 都会一致地返回 true 或一致地返回 false
  • 具有非空性:对于任何非空引用值 x,x.equals(null) 都应返回 false

Object类的 equals 方法实现了对象上最有辨别力的等价关系;也就是说,对于任何非空引用值 x 和 y,只有当 x 和 y 指向同一个对象时,该方法才返回 true(x == y 的值为 true)。

需要注意的是,一般来说,只要重载了 hashCode 方法,就必须重载该方法,以保持 hashCode 方法的一般规则,即相等的对象必须具有相等的散列代码。

== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。

(1)若比较的是基础数据类型,则比较的是他们的值是否相等,比如两个int类型的变量,比较的是变量的值是否一样。

(2)若比较的是引用数据类型,则较的是引用的地址是否相同,比如说新建了两个User对象,比较的是两个User的地址是否一样。

String中有一种特殊场景,String s="a";不同于New对象,它是一个直接量,在常量池中新建"a"。这种形式的字符串,在JVM内部发生字符串拘留,即当声明这样的一个字符串后,JVM会在常量池中先查找有有没有一个值为"a"的对象。如果有,就会把它赋给当前引用.即原来那个引用和现在这个引用指点向了同一对象;如果没有,则在常量池中新创建一个"b",下一次如果有String s1=“b”;又会将s1指向“b”这个对象,即以这形式声明的字符串,只要值相等,任何多个引用都指向同一对象。

package com.donny.demo;

/**
 * @author [email protected]
 * @description
 * @date 2023/8/28
 */
public class Test {

    public static void main(String[] args) {
        Integer a1 = 1;
        Integer a2 = 1;
        String b1 = new String("111");
        String b2 = new String("111");
        String s1 = "222";
        String s2 = "222";

        UserName u1 = new UserName("张三");
        UserName u2 = new UserName("张三");

        System.out.println(a1 == a2);
        System.out.println(a1.equals(a2));

        System.out.println(b1 == b2);
        System.out.println(b1.equals(b2));

        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));

        System.out.println(u1 == u2);
        System.out.println(u1.equals(u2));
    }

    public static class UserName {
        private String name;

        public UserName(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

执行结果

true
true
false
true
true
true
false
false

什么时候需要重写hashCode方法

  1. 只要重写了equals 方法就要重写hashCode方法
  2. 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须覆写这两种方法 。
  3. 如果自定义对象作为 Map 的键,那么必须覆写 hashCode方法 和 equals。

如何重写

简单做法 来自Effective Java

  1. 声明一个int的变量result,用一个非零的常量值,作为第一个关键域的散列值,比如17

  2. 对于对象中每个关键域f(equals方法中涉及的每个域),完成以下步骤:

    1. 为该域计算int类型的散列码c:

      1. 如果该域是boolean类型,则计算(f?1:0)。
      2. 如果该域是bytecharshort或者int类型,则计算(int)f
      3. 如果该域是long类型,则计算(int)(f^(f>>>32))
      4. 如果该域是float类型,则计算Float.floatToIntBits(f)
      5. 如果该域是double类型,则计算Double.doubleToLongBits(f),然后按照步骤2.1.3,为得到的long类型值计算散列值。
      6. 如果该域是一个对象引用,并且该类的equals方法通过递归地调用equals的方式来比较这个域,则同样为这个域递归地调用hashCode。如果需要更复杂的比较,则为这个域计算一个范式(canonical representation),然后针对这个范式调用hashCode。如果这个域的值为null,则返回0(其他常数也行)。
      7. 如果该域是一个数组,则要把每一个元素当做单独的域来处理。也就是说,递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据步骤2.2中的做法把这些散列值组合起来。如果数组域中的每个元素都很重要,可以利用发行版本1.5中增加的其中一个Arrays.hashCode方法。
    2. 按照下面的公式,把步骤2.1中计算得到的散列码c合并到result中:result = 31 * result + c; //此处31是个奇素数,并且有个很好的特性,即用移位和减法来代替乘法,可以得到更好的性能:31*i == (i<<5) - i, 现代JVM能自动完成此优化。

  3. 返回result

  4. 检验并测试该hashCode实现是否符合通用约定。

对hash冲突要求高的,可以参考谷歌Guava的Hashing类。

JDK String的重写例子

    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) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

你可能感兴趣的:(JAVA,java,python,开发语言)