hashCode()和equals()

equals()的作用

equals()用来判断两个对象的内存地址是否相等
Object类定义了equals,意味着所有的Java类都实现了equals()方法比较两个对象是否相等。

Object源码中,默认的equals方法等价于 ==
所以,通常会重写equals方法,比如String类库里的equals比较两个对象的内容相等。

类是否覆盖equals()方法

  1. 没有重写equals方法
import java.util.*;
import java.lang.Comparable;

/**
 * @desc equals()的测试程序。
 *
 * @author skywang
 * @emai kuiwu-wang@163.com
 */
public class EqualsTest1{

    public static void main(String[] args) {
        // 新建2个相同内容的Person对象,
        // 再用equals比较它们是否相等
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        System.out.printf("%s\n", p1.equals(p2));
    }

    /**
     * @desc Person类。
     */
    private static class Person {
        int age;
        String name;

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

        public String toString() {
            return name + " - " +age;
        }
    }
}

运行结果:
false

结果分析:我们通过 p1.equals(p2) 比较是否相等时,实际上调用的是Object.equlas()方法(即默认的 p1==p2),它是比较 p1和p2是否是同一个对象。而由p1和p2的定义可知,它们内容虽然相同,但它们是两个不同的对象,所以返回false。

  1. 重写equals方
import java.util.*;
import java.lang.Comparable;

/**
 * @desc equals()的测试程序。
 *
 * @author skywang
 * @emai kuiwu-wang@163.com
 */
public class EqualsTest2{

    public static void main(String[] args) {
        // 新建2个相同内容的Person对象,
        // 再用equals比较它们是否相等
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        System.out.printf("%s\n", p1.equals(p2));
    }

    /**
     * @desc Person类。
     */
    private static class Person {
        int age;
        String name;

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

        public String toString() {
            return name + " - " +age;
        }

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

            //如果是同一个对象返回true,反之返回false
            if(this == obj){
                return true;
            }

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

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

运行结果:true
结果分析:重写了Person的equals()方法后,当两个Person对象的 name 和 age 都相等,返回true

equals()和 == 的区别

== 判断两个对象的地址是否相等,及判断是否同一个对象
equals() 也是判断两个对象是否相等,但分为重写和未被重写两种情况:
1. 类没有覆盖equals方法,通过equals方法比较该类的两个对象是否相等,等价于 ==
2. 类覆盖了equals方法,如果该类的两个对象的内容相等,返回true

hashCode()的作用

hashCode()的作用是获取哈希码,也称为散列码,它实际上是返回一个int整数,用来确定该对象在哈希表中的索引位置。

hashCode()定义在JDK的Object类中,所以任何类都实现了hashCode()方法。
hashCode()在散列表中才有用,在其它情况下(例如,创建类的单个对象,或者创建类的对象数组等)没用。
在散列表中hashCode()的作用是获取对象的散列码,用来确定该对象在散列表中的位置

散列码:散列表存储的是键值对,能根据k快速检索v,这其中就用到了散列码。
散列表的本质是通过数组实现的,当我们要获取散列表中的某个v时,实际上是要获取数组中的某个位置的元素,而数组的位置,是通过k来获取,更进一步来说,数组的位置是通过k对应的散列码计算得到的。

以HashSet为例:
假设 HashSet中已经有1000个元素,当插入第1001个元素时,需要怎么处理?
HashSet是Set集合,允许有重复元素。
“将第1001个元素逐个和前面1000个元素进行比较?”,显然效率低下。
用散列表能解决这个问题,它根据元素的散列码计算出元素在散列表中的位置,然后将元素插入该位置即可。对于相同的元素,自然只保存了一个。

由此可知,若两个元素相等,它们的散列码以定向等;反之则不一定。
在散列表中:
1. 如果两个对象相等,那它们的hashCode()一定相同;
2. 如果两个对象的hashCode()相等,它们并不一定相等。
注意:这是在散列表情况中,在非散列表中一定都相等。

hashCode()和equals()的关系

从 类的用途 来区分:

  1. 第一种 不会创建“类对应的散列表”
    这里所说的不会创建类对应的散列表是指,不会在HashSet、HashTable、HashMap等本质是散列表的数据结构中用到该类。
    在这种情况下,该类的hashCode()和equals()各不相关
    在这种情况下,equals()用来比较该类的两个对象是否相等,而hashCode()毫无作用。
package com.example.demo.ctrl;

import lombok.Data;

import java.util.Objects;

@Data
public class Person {
    int age;
    String name;

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

        //如果是同一个对象返回true,反之返回false
        if (this == o) {
            return true;
        }

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

        Person person = (Person)o;
        return name.equals(person.name) && age==person.age;
    }
}

在重写对象的equlas()方法使得判断对象内容是否一致的情况下,如果两个对象相等,对象各自的hashCode()也不一定相等

  1. 第二种 创建了 “类对应的散列表”
    这里所说的“会创建类对应的散列表”是指,会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,会创建该类的HashSet集合。

在这种情况下,该类的 hashCode()和equals() 是有关系的:

  • 如果两个对象相等,它们的hashCode()值一定相同。
    这里的相等是指通过equals()比较两个对象时返回true
  • 如果两个对象的hashCode()相等,它们并不一定相等。
    因为在散列表中,hashCode()相等,即两个kv对的哈希值相等,然后哈希值相等并不一定能得出键值对相等。

在这种情况下,如果要判断两个对象是否相等,equals()和hashCode()都要覆盖重写。

例如,创建Person类的HashSet集合,必须同时覆盖Person类的equals() 和 hashCode()方法。
如果单单只是覆盖equals()方法。我们会发现,equals()方法没有达到我们想要的效果。

import java.util.*;
import java.lang.Comparable;

/**
 * @desc 比较equals() 返回true 以及 返回false时, hashCode()的值。
 *
 * @author skywang
 * @emai kuiwu-wang@163.com
 */
public class ConflictHashCodeTest1{

    public static void main(String[] args) {
        // 新建Person对象,
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        Person p3 = new Person("aaa", 200);

        // 新建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);
    }

    /**
     * @desc Person类。
     */
    private static class Person {
        int age;
        String name;

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

        public String toString() {
            return "("+name + ", " +age+")";
        }

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

            //如果是同一个对象返回true,反之返回false
            if(this == obj){
                return true;
            }

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

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

运行结果:

p1.equals(p2) : true; p1(1169863946) p2(1690552137)
set:[(eee, 100), (eee, 100), (aaa, 200)]

结果分析:
只重写了equals(),但是类创建了对应的散列表却未重写hashCode(),所以HashSet里仍然有重复元素。
虽然p1和p2内容相等,但是它们的hashCode()不同,所以HashSet在添加p1和p2的时候认为他们不相等。

下面同时覆盖equals()和hashCode()

import java.util.*;
import java.lang.Comparable;

/**
 * @desc 比较equals() 返回true 以及 返回false时, hashCode()的值。
 *
 * @author skywang
 * @emai kuiwu-wang@163.com
 */
public class ConflictHashCodeTest2{

    public static void main(String[] args) {
        // 新建Person对象,
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        Person p3 = new Person("aaa", 200);
        Person p4 = new Person("EEE", 100);

        // 新建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());
        // 比较p1 和 p4, 并打印它们的hashCode()
        System.out.printf("p1.equals(p4) : %s; p1(%d) p4(%d)\n", p1.equals(p4), p1.hashCode(), p4.hashCode());
        // 打印set
        System.out.printf("set:%s\n", set);
    }

    /**
     * @desc Person类。
     */
    private static class Person {
        int age;
        String name;

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

        public String toString() {
            return name + " - " +age;
        }

        /**
         * @desc重写hashCode
         */
        @Override
        public int hashCode(){
            int nameHash =  name.toUpperCase().hashCode();
            return nameHash ^ age;
        }

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

            //如果是同一个对象返回true,反之返回false
            if(this == obj){
                return true;
            }

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

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

运行结果:

p1.equals(p2) : true; p1(68545) p2(68545)
p1.equals(p4) : false; p1(68545) p4(68545)
set:[aaa - 200, eee - 100]

结果分析:
重写hashCode()和equals()后,HashSet能正确去重
比较p1和p2,它们的hashCode()相等,通过equals()比较它们也返回true,所以p1和p2被视为相等。
比较p1和p4,虽然它们的hashCode()相等,但是通过equals()比较返回false,所以p1和p4被视为不相等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值