重写equals方法 和 hashcode 方法

问题:

equals和hashCode有什么作用?他们是什么关系?

在java中,所有的对象都是继承于Object类,对象比较默认调用的是Object的equals方法 和 hashcode 方法
这两个方法是用来判断对象是否相等;

equals和hashCode源码:

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

equals:
Object的equals方法的源码还是使用的==,比较两个对象的内存地址
1.对于值对象,==比较的是两个对象的值 2.对于引用对象,比较的是两个对象的地址
hashCode:
Object的hashCode方法是基于对象的ID实现的,是根据内存地址换算出来的一个值,不同的对象可能存在相等的hashcode值;
它是一个native方法,可以根据平台自行实现,但它对其实现提出了一定的要求:

  • 在同样的条件下,同一个对象无论运行多少次,返回的hash值必须一样。
  • 如果两个对象通过equals方法比较判定相等,则他们通过hashCode方法返回的hash值也必须相等。
  • 如果两个对象通过equals方法比较判定不相等,则不必保证他们通过hashCode返回的hash值不相等。

这两个方法的作用就是为了对象间的比较,而他们之间的关系都和其方法的规则和约束有关

对于没有重写equals和hashCode时的规定如下:
如果两个对象通过equals方法比较相等,则他们hashCode的返回值一定要相等。但如果两个对象的hashCode值相等,他们通过equals方法比较的返回值则不一定相等。
如果两个对象hashCode的返回值相等,不能判断两个对象是相等的。但如果两个对象的hashCode的返回值不相等,则可以判定两个对象一定不相等。

以上所说的规则可以简单归纳为:

  • 两个对象相等,hashCode一定相等
  • 两个对象不等,hashCode不一定不等
  • hashCode相等,两个对象不一定相等
  • hashCode不等,两个对象一定不等

为什么要重写equals方法 和 hashcode 方法

在实际的业务场景中,引用对象间的比较,引用对象的内容相同,我们就认为是同一个对象,而Object的equals方法 和 hashcode 方法是通过内存地址比较的,内容相同的对象地址值可能不一样,所以为了满足实际的业务场景,需要重写equals方法 和 hashcode 方法;

  • 重写前的equals方法 和 hashcode 方法基于对象的ID实现,比较的是内存地址,两个内容相等的引用对象的内存地址可能不一样,所以需要重写equals方法和 hashcode方法,根据内容判断两个引用对象是否相同;
  • 重写后的equals方法 和 hashcode 方法基于对象的内容实现,比较的是对象的内容;

为什么要同时重写equals方法和hashcode方法

  • 这是一个通用约定,hashCode方法的常规约定,它声明相等的对象必须有相等的哈希码;
  • 相同的对象的hashCode 的散列值最好保持相等, 而不同对象的散列值,我们也使其保持不相等。重写的equals方法保证对象的内容相同,内容相等的对象hashCode方法的散列值也必须相等,这才是对象相等的结果;

重写hashCode方法提升hashmap的性能:

重写了hashCode方法后,能够确保两个对象返回的的hashCode散列值是不一样的,这样一来,
在我们使用hashmap 去存储对象, 在进行验重逻辑的时候,咱们的性能就特别好了。
hashmap在插入值时对key(这里key是对象)的验重:
HashMap中的比较key:
先求出key的hashcode(),比较其值是否相等;
-若相等再比较equals(),若相等则认为他们是相等的。
-若equals()不相等则认为他们不相等。
如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。

而HashSet,
用来判断key是否相等的方法,其实也是调用了HashMap加入元素方法,再判断是否相等。

使用list.contains()方法需重写equals():

它直接调用内部的indexof()方法去比较,可以看到内部是拿参数对象的equals()方法去比较,这个源生object对象的equals()方法, 可以看到比较的是两个对象的引用,重写equals()方法比较对象内容

场景:

我们现在需要比较两个对象 Pig 是否相等 。
而Pig 对象里面包含 三个字段, name,age,nickName ,我们现在只需要认为如果两个pig对象的name名字和age年龄一样,那么这两个pig对象就是一样的,nickName昵称不影响相等的比较判断。

    public static void main(String[] args) {
        Pig pig1=new Pig();
        pig1.setName("A");
        pig1.setAge(11);
        pig1.setNickName("a");
 
        String name= new String("A");
        Pig pig2=new Pig();
        pig2.setName(name);
        pig2.setAge(11);
        pig2.setNickName("B");
 
        System.out.println(pig1==pig2);  //false
        System.out.println(pig1.equals(pig2)); //false
        System.out.println(pig1.hashCode() ==pig2.hashCode()); //false
    }

pig1和pig2都是新new出来的,内存地址都是不一样的。

  • == : 比较内存地址 ,那肯定是false了 ;
  • equals: 默认调用的是Object的equals方法,看下面源码图,显然还是使用了== ,那就还是比较内存地址,那肯定是false了;
  • hashCode: 这是根据一定规则例如对象的存储地址,属性值等等映射出来的一个散列值,不同的对象存在可能相等的hashcode,但是概率非常小(两个对象equals返回true时,hashCode返回肯定是true;而两个对象hashCode返回true时,这两个对象的equals不一定返回true; 还有,如果两个对象的hashCode不一样,那么这两个对象一定不相等!)。
    一个好的散列算法,我们肯定是尽可能让不同对象的hashcode也不同,相同的对象hashcode也相同。这也是为什么我们比较对象重写equals方法后还会一起重写hashcode方法。

接下来,重写equals方法和hashCode方法,实现我们这个Pig对象的比较,只要能保证name和age两个字段属性一致,就返回相等true。

重写equals方法:

 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Pig pig = (Pig) o;
        boolean nameCheck=false;
        boolean ageCheck=false;
        
        if (this.name == pig.name) {
            nameCheck = true;
        } else if (this.name != null && this.name.equals(pig.name)) {
            nameCheck = true;
        }
 
        if (this.age == pig.age) {
            ageCheck = true;
        } else if (this.age != null && this.age.equals(pig.age)) {
            ageCheck = true;
        }
 
       if (nameCheck && ageCheck){
           return true;
       }
 
       return  false;
    }

重写hashCode方法:

重写hashcode方法的时候,为什么要写个31啊?
之所以使用 31, 是因为他是一个奇素数。
如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算(低位补0)。
使用素数的好处并不很明显,但是习惯上使用素数来计算散列结果。
31 有个很好的性能,即用移位和减法来代替乘法,
可以得到更好的性能: 31 * i == (i << 5)- i,
现代的 VM 可以自动完成这种优化。这个公式可以很简单的推导出来。
为什么还要个数字17?
这个其实道理一样,在《Effective Java》里,作者推荐使用的就是 基于17和31的散列码的算法 ,而在Objects里面的hash方法里,17换做了1 。

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

手动重写:

public class MenuResource {
    /**
     * <pre>
     * 自增id
     * 表字段 : menu_of_resource.id
     * </pre>
     */
    private Integer id;

    /**
     * <pre>
     * 菜单id
     * 表字段 : menu_of_resource.menu_id
     * </pre>
     */
    private String menuId;

    /**
     * <pre>
     * 资源id
     * 表字段 : menu_of_resource.resource_id
     * </pre>
     */
    private String resourceId;

    public Integer getId() {
        return id;
    }

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

    public String getMenuId() {
        return menuId;
    }

    public void setMenuId(String menuId) {
        this.menuId = menuId == null ? null : menuId.trim();
    }

    public String getResourceId() {
        return resourceId;
    }

    public void setResourceId(String resourceId) {
        this.resourceId = resourceId == null ? null : resourceId.trim();
    }

    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null) {
            return false;
        }
        if (getClass() != that.getClass()) {
            return false;
        }
        MenuResource other = (MenuResource) that;
        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
            && (this.getMenuId() == null ? other.getMenuId() == null : this.getMenuId().equals(other.getMenuId()))
            && (this.getResourceId() == null ? other.getResourceId() == null : this.getResourceId().equals(other.getResourceId()));
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
        result = prime * result + ((getMenuId() == null) ? 0 : getMenuId().hashCode());
        result = prime * result + ((getResourceId() == null) ? 0 : getResourceId().hashCode());
        return result;
    }

    @Override
public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append(getClass().getSimpleName());
    sb.append(" [");
    sb.append("Hash = ").append(hashCode());
    sb.append(", id=").append(id);
    sb.append(", menuId=").append(menuId);
    sb.append(", resourceId=").append(resourceId);
    sb.append("]");
    return sb.toString();
}

其他重写方式:

1.java 7 有在Objects里面新增了我们需要重新的这两个方法,所以我们重写equals和hashCode还可以使用java自带的Objects,如:

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Pig pig = (Pig) o;
        return Objects.equals(name, pig.name) &&
                Objects.equals(age, pig.age);
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

2.lombok的注解,让它帮我们写,我们自己就写个注解
用@Data注解应该基本就够用了,它结合了@ToString,@EqualsAndHashCode,@Getter和@Setter的功能。
image
然后编译后,可以看到lombok帮我们重写了equals和hashcode方法

posted @   Jimmyhus  阅读(346)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示