浅谈 equals() 和 hashCode()()

和 在 Object 类中定义

和 在 Object 类中定义

equals()
hashCode()
  • hashCode():用于计算对象哈希值。
  • equals():用于比较对象是否相等。

默认实现

定义的方法

定义的方法

Object

默认实现:均基于对象引用地址。

  • equals():比较对象引用地址。
  • hashCode():返回对象引用地址(本地方法)。
    public boolean equals(Object obj) {
    return (this == obj);
    }

    public native int hashCode();

方法重写

① 原因

为什么要重写?

为什么要重写?

  • 哈希集合的检索机制中,使用了 hashCode() 和 equals()。
  • 在默认实现下,只有同一个对象引用的 equals() 和 hashCode() 才会相等。
  • 在实际业务中,通常认为两个对象属性相同则对象相等。
  • 需要成对重写 hashCode() 和 equals(),才能实现对象的区分性。

示例

示例

HashMap<User> userMap = new HashMap<>;
User u1 = new User("secret", 20);
User u2 = new User("secret", 20);
userMap.put(u1, "user1");

userMap.get(u2);	// 能获取到吗?
  • 未重写的情况:不能。因为 u1 和 u2 是不同的对象引用,hashcode() 和 equals() 比较不相等。
  • 重写的情况:能。因为自定义了 hashCode() 计算规则、equals() 比较条件,u1 和 u2 视为相等的对象。

② 重写 equals()

通常认为:对象的属性值完全相同,则对象相等。

通常认为:对象的属性值完全相同,则对象相等。

  • 参数检查:

    相同引用
    判空
    判类型:

    getClass()(严格):返回运行时具体类型,不考虑继承关系。
    instanceOf(宽松):测试对象是否为类的实例(或接口实现类)。

  • 相同引用
  • 判空
  • 判类型:

    getClass()(严格):返回运行时具体类型,不考虑继承关系。
    instanceOf(宽松):测试对象是否为类的实例(或接口实现类)。

  • getClass()(严格):返回运行时具体类型,不考虑继承关系。
  • instanceOf(宽松):测试对象是否为类的实例(或接口实现类)。
  • 比较属性:先向下转型,再比较属性值。

    基本类型:使用 ==。

    引用类型:使用 Objects.equals() 方法,避免 NPE。
    @Override
    public boolean equals(Object obj) {
    if (this == obj) {
    return true;
    }

    // 若严格要求类型一致,条件二改成 this.getClass() != obj.getClass()
    if (obj == null || !obj instanceof Person) {
    return false;
    }

    Person p = (Person) obj;

    // 不使用 xxx.equals(xxx),避免NPE
    return Objects.equals(this.name, p.name)
    && this.age == p.age;

    }

  • 基本类型:使用 ==。
  • 引用类型:使用 Objects.equals() 方法,避免 NPE。
    @Override
    public boolean equals(Object obj) {
    if (this == obj) {
    return true;
    }

    // 若严格要求类型一致,条件二改成 this.getClass() != obj.getClass()
    if (obj == null || !obj instanceof Person) {
    return false;
    }

    Person p = (Person) obj;

    // 不使用 xxx.equals(xxx),避免NPE
    return Objects.equals(this.name, p.name)
    && this.age == p.age;

    }

③ 重写 hashCode()

原则:尽量避免哈希冲突。
此处使用 工具类的方法。

原则:尽量避免哈希冲突。

此处使用 工具类的方法。

Objects
@Override
public int hashCode() {
    return Objects.hash(name, age);
}

分析: 的

分析: 的

Objects
hash(...)
  • Objects.hash(…):

    可变参数(数组)

    调用 Arrays.hashCode(…) 计算哈希值。
    public static int hash(Object… values) {
    return Arrays.hashCode(values);
    }

  • 可变参数(数组)
  • 调用 Arrays.hashCode(…) 计算哈希值。
    public static int hash(Object… values) {
    return Arrays.hashCode(values);
    }

  • Arrays.hashCode(…):哈希算法 hash = 31 * hash + hash(i)

    基于多个属性计算。

    基于迭代的方式计算。
    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;
    }

  • 基于多个属性计算。
  • 基于迭代的方式计算。
    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;
    }

思考

选用 作为乘子的好处

选用 作为乘子的好处

31
  • 质数:在计算时可以尽量减少哈希冲突。
  • 性能优化:

    代码运行频繁时,JVM 会将 31 * i 优化为位运算 (i<<5) - 1。 位运算的执行效率高。

  • 代码运行频繁时,JVM 会将 31 * i 优化为位运算 (i<<5) - 1。
  • 位运算的执行效率高。

方法成对重写后, 和 的关系

方法成对重写后, 和 的关系

equals()
hashCode()

结论

  • equals() 和 hashCode() 都相等,才认为对象相同。
  • 若 equals() 相等,则 hashCode() 相等。

    描述
    成立?

    逆命题
    若 hashCode() 相等,则 equals() 相等
    (哈希冲突)

    否命题
    若 equals() 不相等,则 hashCode() 不相等
    (哈希冲突)

    逆否命题
    若 hashCode 不相等,则 equals() 不相等

描述 成立?
逆命题 hashCode() 相等,则 equals() 相等 (哈希冲突)
否命题 equals() 不相等,则 hashCode() 不相等 (哈希冲突)
逆否命题 hashCode 不相等,则 equals() 不相等
hashCode()
equals()
equals()
hashCode()
hashCode
equals()
————————

和 在 Object 类中定义

和 在 Object 类中定义

equals()
hashCode()
  • hashCode():用于计算对象哈希值。
  • equals():用于比较对象是否相等。

默认实现

定义的方法

定义的方法

Object

默认实现:均基于对象引用地址。

  • equals():比较对象引用地址。
  • hashCode():返回对象引用地址(本地方法)。
    public boolean equals(Object obj) {
    return (this == obj);
    }

    public native int hashCode();

方法重写

① 原因

为什么要重写?

为什么要重写?

  • 哈希集合的检索机制中,使用了 hashCode() 和 equals()。
  • 在默认实现下,只有同一个对象引用的 equals() 和 hashCode() 才会相等。
  • 在实际业务中,通常认为两个对象属性相同则对象相等。
  • 需要成对重写 hashCode() 和 equals(),才能实现对象的区分性。

示例

示例

HashMap<User> userMap = new HashMap<>;
User u1 = new User("secret", 20);
User u2 = new User("secret", 20);
userMap.put(u1, "user1");

userMap.get(u2);	// 能获取到吗?
  • 未重写的情况:不能。因为 u1 和 u2 是不同的对象引用,hashcode() 和 equals() 比较不相等。
  • 重写的情况:能。因为自定义了 hashCode() 计算规则、equals() 比较条件,u1 和 u2 视为相等的对象。

② 重写 equals()

通常认为:对象的属性值完全相同,则对象相等。

通常认为:对象的属性值完全相同,则对象相等。

  • 参数检查:

    相同引用
    判空
    判类型:

    getClass()(严格):返回运行时具体类型,不考虑继承关系。
    instanceOf(宽松):测试对象是否为类的实例(或接口实现类)。

  • 相同引用
  • 判空
  • 判类型:

    getClass()(严格):返回运行时具体类型,不考虑继承关系。
    instanceOf(宽松):测试对象是否为类的实例(或接口实现类)。

  • getClass()(严格):返回运行时具体类型,不考虑继承关系。
  • instanceOf(宽松):测试对象是否为类的实例(或接口实现类)。
  • 比较属性:先向下转型,再比较属性值。

    基本类型:使用 ==。

    引用类型:使用 Objects.equals() 方法,避免 NPE。
    @Override
    public boolean equals(Object obj) {
    if (this == obj) {
    return true;
    }

    // 若严格要求类型一致,条件二改成 this.getClass() != obj.getClass()
    if (obj == null || !obj instanceof Person) {
    return false;
    }

    Person p = (Person) obj;

    // 不使用 xxx.equals(xxx),避免NPE
    return Objects.equals(this.name, p.name)
    && this.age == p.age;

    }

  • 基本类型:使用 ==。
  • 引用类型:使用 Objects.equals() 方法,避免 NPE。
    @Override
    public boolean equals(Object obj) {
    if (this == obj) {
    return true;
    }

    // 若严格要求类型一致,条件二改成 this.getClass() != obj.getClass()
    if (obj == null || !obj instanceof Person) {
    return false;
    }

    Person p = (Person) obj;

    // 不使用 xxx.equals(xxx),避免NPE
    return Objects.equals(this.name, p.name)
    && this.age == p.age;

    }

③ 重写 hashCode()

原则:尽量避免哈希冲突。
此处使用 工具类的方法。

原则:尽量避免哈希冲突。

此处使用 工具类的方法。

Objects
@Override
public int hashCode() {
    return Objects.hash(name, age);
}

分析: 的

分析: 的

Objects
hash(...)
  • Objects.hash(…):

    可变参数(数组)

    调用 Arrays.hashCode(…) 计算哈希值。
    public static int hash(Object… values) {
    return Arrays.hashCode(values);
    }

  • 可变参数(数组)
  • 调用 Arrays.hashCode(…) 计算哈希值。
    public static int hash(Object… values) {
    return Arrays.hashCode(values);
    }

  • Arrays.hashCode(…):哈希算法 hash = 31 * hash + hash(i)

    基于多个属性计算。

    基于迭代的方式计算。
    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;
    }

  • 基于多个属性计算。
  • 基于迭代的方式计算。
    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;
    }

思考

选用 作为乘子的好处

选用 作为乘子的好处

31
  • 质数:在计算时可以尽量减少哈希冲突。
  • 性能优化:

    代码运行频繁时,JVM 会将 31 * i 优化为位运算 (i<<5) - 1。 位运算的执行效率高。

  • 代码运行频繁时,JVM 会将 31 * i 优化为位运算 (i<<5) - 1。
  • 位运算的执行效率高。

方法成对重写后, 和 的关系

方法成对重写后, 和 的关系

equals()
hashCode()

结论

  • equals() 和 hashCode() 都相等,才认为对象相同。
  • 若 equals() 相等,则 hashCode() 相等。

    描述
    成立?

    逆命题
    若 hashCode() 相等,则 equals() 相等
    (哈希冲突)

    否命题
    若 equals() 不相等,则 hashCode() 不相等
    (哈希冲突)

    逆否命题
    若 hashCode 不相等,则 equals() 不相等

描述 成立?
逆命题 hashCode() 相等,则 equals() 相等 (哈希冲突)
否命题 equals() 不相等,则 hashCode() 不相等 (哈希冲突)
逆否命题 hashCode 不相等,则 equals() 不相等
hashCode()
equals()
equals()
hashCode()
hashCode
equals()