看完这篇文章,我彻底懂了 equals( ) 和 hashCode( )

流过昼夜 提交于 2020-03-07 13:11:27

总览equals( ) 方法

  • 理解equals()方法和==运算符的区别是非常重要的。

    • 默认情况下(即没有被重写时)equals()只能比较引用类型,"=="既能 比较引用类型又能比较基本类型。
    • equals()方法从Object类继承,即比较对象引用的值
    • 一般都被子类方法覆盖,不再比较引用的值
  • "=="运算符:

    • 比较基本数据类型:相当于算术等号。
    • 比较引用数据类型:比较引用的值,不能被覆盖。
  • 通常情况,子类要重写equals( ),改变它的含义。所以有的类中 equals( )是比较地址,有的类中该方法就不比较地址,具体的,就看子类新定义的该方法的规定。建议看看包装类中的equals()方法,蛮有趣的。

  • 在Java中有个规定:如果equals( )返回两个对象是相等的,那这两个对象上调用hashCode( )返回的整数必须相等。否则在使用Hash类型集合时就会产生错误。

  • 注意:覆盖equals( )方法同时,还要记得覆盖hashCode( )方法。 需要说明,如果equals( )返回两个对象不等,它们的hashCode( )也 可以返回相同的整数。但是最好让它们的hashCode( )返回不同的整 数,这有利于提高Hash类型集合的性能。

  • 重写equals方法时,一定要重写hashcode()方法吗?

    • hashcode的调用的条件:

      • 想往map里面放一个类作为map的键值,这个类又是自己设计的;
      • 虽然类不是自己写的,但是你修改了这个类的equals方法;
    • 如果满足上述调用条件,就要注意重写hashcode方法。
      这样 当你往map 里放值得时候,系统会调用这个对象的.hashcode()方法来生成相应的 hash值,来映射相应的对象。

  • 如果同一个类的两个对象的属性值相等,那么他们的hashcode一定相等吗?这个要看你具体如何实现你的hashcode,如果你希望他们的值一样hashcode也一样,你就可以这样实现。 但是hashcode的实现,一般要满足几个特征,比如自反性、对称性、传递性那些。

等价关系与equals( )

离散数学的知识:

设R是A上关系,若R是自反的、对称的和传递的,则称R是A中的等价关系。
a,bAaRbab若 a,b\in A,且aRb,则称a与b等价

equals() 其实就是一种等价关系,它指定了某种方法(默认的hashCode()、重写的hashCode()、自定义的equals()判据等)来判定这种等价关系。

我们自己想要重写equals(),为了保证这种等价关系,就需要维护自反性、对称性、传递性。

其实,简单的测试还不太够,最好是有形式化的证明。

这篇文章中就涉及了equals()的自反性、对称性、传递性,做了简单的测试……
其实我也很无奈,这是我在准备大学里一门核心专业课考试的前一晚总结的,内心真正比较喜欢的文章,but没人看,Readers更喜欢比较浮夸的文章

IDE自动生成equals( )和hashCode( )

如果你确实懒得重写equals()和hashCode(),IDE支持自动生成这两个方法。

首先,Person类已经完成到了这个程度:

import java.io.Serializable;

public class Person implements Serializable {

    private static final long serialVersionUID = 1L;

    private Integer id;

    private String name;

    private Integer age;

    public Person(Integer id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

}

Eclipse

在这里插入图片描述
在这里插入图片描述

import java.io.Serializable;
import java.util.Objects;

public class Person implements Serializable {

    private static final long serialVersionUID = 1L;

    private Integer id;

    private String name;

    private Integer age;

    public Person(Integer id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

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

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof Person)) {
            return false;
        }
        Person other = (Person) obj;
        return Objects.equals(id, other.id) && Objects.equals(name, other.name);
    }

}

IntelliJ IDEA

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import java.io.Serializable;
import java.util.Objects;

public class Person implements Serializable {

    private static final long serialVersionUID = 1L;

    private Integer id;

    private String name;

    private Integer age;

    public Person(Integer id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Person{" + "id=" + id + ", name='" + name + '\'' + ", age=" + 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 Objects.equals(id, person.id) &&
                Objects.equals(name, person.name);
    }

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

String的hashCode( )

String有hash这个实例变量,它的定义如下:

private int hash; // Default to 0

它缓存了hashCode()方法的值,也就是说,第一次调用hashCode()的时候,会把结果保存在hash这个变量中,以后再调用就直接返回保存的值。

我们来看下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;
}

如果缓存的hash不为0,就直接返回了,否则根据字符数组中的内容计算hash,计算方法是:

s[0]31(n1)+s[1]31(n2)++s[n1]30s[0]*31^{(n-1)} + s[1]*31^{(n-2)} + \cdots + s[n-1] * 3^0

s表示字符串,s[0]表示第一个字符,n表示字符串长度,s[0]31(n1)s[0]*31^{(n-1)}表示31的n-1次方再乘以第一个字符的值。

为什么要用这个计算方法呢?这个式子中,hash值与每个字符的值有关,每个位置乘以不同的值,hash值与每个字符的位置也有关。使用31大概是因为两个原因,一方面可以产生更分散的散列,即不同字符串hash值也一般不同,另一方面计算效率比较高,31* h 与 32 * h - h 即 (h<<5)-h 等价,可以用更高效率的移位和减法操作代替乘法操作。

在Java中,普遍采用以上思路来实现hashCode,也就是所谓的"33-Times"算法,多看源码,其实Java很多地方都是这样的思路。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!