为什么重写hashCode()时,必须重写equals()?

简介

1、保证单一原则:equals相同的两个对象的hashcode必须相同。如果重写了equals而没有重写hashcode,会出现equals相同hashcode不相同这个现象。

2、在无序集合中(如Set),使用hashcode来计算key应存储在hash表的索引,如果重写了equals而没有重写hashcode,会出现两个完全相同的对象。因为hashcode不同,计算出的索引不同,那么这些集合就会混乱。

3、提高效率,当比较两个对象是否相同时,先比较hashcode是否相同,如果hashcode不相同肯定不是一个对象,如果hashcode相同再调用equals来进行比较,减少了比较次数从而提高效率。


数据存储或读取的方式

1、普通方式

2、hashCode存储或读取的方式

        从上图可以明显看出,hashCode读写数据的效率明显高于普通方式。因此当我们对比两个对象是否相等时,我们就可以先使用 hashCode 进行比较,如果比较的结果是 true,那么就可以使用 equals 再次确认两个对象是否相等,如果比较的结果也是 true,那么这两个对象就是相等的,否则就认为两个对象不相等。这样就大大的提升了比较的效率,这也是为什么开发时使用 hashCode 和 equals 协同的方式来进行确认两个对象是否相等的原因。

        那为什么不直接使用 hashCode 确定两个对象是否相等呢?这是因为不同对象的 hashCode 可能相同,但 hashCode 不同的对象一定不相等,所以使用 hashCode 可以起到快速初次判断对象是否相等的作用从而提高效率。


equals()方法

        Object 类中的 equals ()方法用于检测一个对象是否等于另外一个对象。在 Object 类中,这个方法将判断两个对象是否具有相同的引用。如果两个对象具有相同的引用,它们一定是相等的。

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

        但是大多数情况直接通过equals ()的判断是没有什么意义的,参照如下实例

public class Test {
	public static void main(String[] args) {
	        Person u1 = new Person();
	        u1.setName("您好");
	        u1.setAge(18);
	  
	        Person u2 = new Person();
	        u1.setName("您好");
	        u1.setAge(18);
	        
	        //打印equals()结果
	        System.out.println("两个对象是否相同:" + u1.equals(u2));
	    }
	}
	 
	class Person {
	    private String name;
	    private int age;
	    
	    public String getName() {
	        return name;
	    }
	    
	    public void setName(String name) {
	        this.name = name;
	    }
	    
	    public int getAge() {
	        return age;
	    }
	    
	    public void setAge(int age) {
	        this.age = age;
	    }
	
}

        输出结果如下

        不难看出即使两个对象完全相等但是返回值依然是false,因此判断两个对象是否相等一定要重写equals()方法


hashCode()方法

        hashCode 代表散列码,它是由对象推导出的一个整型值,并且这个值为任意整数,包括正数或负数。需要注意的是:散列码是没有规律的。如果 x 和 y 是两个不同的对象,x.hashCode() 与 y.hashCode() 基本上不会相同。但如果 a 和 b 相等,则 a.hashCode() 一定等于 b.hashCode()。

public native int hashCode();

        从上述源码可以看到,Object 类中的 hashCode 调用了一个(native)本地方法,返回了一个 int 类型的整数(可能是正数也可能是负数)。

        那么hashCode相等代表两个对象一定相等吗?请看下述实例

public class Test {
	 public static void main(String[] args) {
	     String s1 = "Hello";
	     String s2 = "Hello";
	     String s3 = "您好";
	     
	     String str1 = "Aa";
	     String str2 = "BB";
	     
	     System.out.println("s1 hashCode:" + s1.hashCode());
	     System.out.println("s2 hashCode:" + s2.hashCode());
	     System.out.println("s3 hashCode:" + s3.hashCode());
	     
	     System.out.println("str1 hashCode:" + str1.hashCode());
	     System.out.println("str2 hashCode:" + str2.hashCode());
	    	        	        
	 }

}

        输出结果如下

        不难看出即使两个对象不相等,它们的hashCode也有可能相同,因此判断两个对象是否相等同样必须对hashCode()方法进行重写。


为什么equals()方法和hashCode()方法必须一起重写

        Set集合的特点没有重复的元素,因此我们以它为例来阐述为何equals()方法和hashCode()方法必须一起重写。

public class Test {
	 public static void main(String[] args) {
		 HashSet<String> has = new HashSet<String>();
			has.add("李白");
			has.add("王维");
			has.add("李白");
			has.add("杜甫");
			has.add("杜甫");
			has.add("白居易");
			has.add("苏轼");
			System.out.println(has);
	    	        	        
	 }

}

        输出结果如下

        可以看到,重复的元素并没有成功添加到集合中。那么,如果我们只重写equals()方法会发生什么呢?

public class Test {
    public static void main(String[] args) {
        // 对象 1
        Person p1 = new Person();
        p1.setName("您好");
        p1.setAge(18);
        // 对象 2
        Person p2 = new Person();
        p2.setName("您好");
        p2.setAge(18);
        // 创建 Set 集合
        Set<Person> set = new HashSet<Person>();
        set.add(p1);
        set.add(p2);
        // 打印 Set 中的所有数据
        set.forEach(p -> {
            System.out.println(p);
        });
    }
}
 
 
class Person {
    private String name;
    private int age;
 
    // 只重写了 equals 方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true; // 引用相等返回 true
        // 如果等于 null,或者对象类型不同返回 false
        if (o == null || getClass() != o.getClass()) return false;
        // 强转为自定义 Person 类型
        Person person = (Person) o;
        // 如果 age 和 name 都相等,就返回 true
        return age == person.age &&
                Objects.equals(name, person.name);
    }
 
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
     @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

        输出结果如下

        不难看出,即使两个对象是相等的,Set 集合也没有将二者进行去重与合并。这就是重写了 equals ()方法但没有重写 hashCode ()方法的问题所在。

        下面我们对hashCode ()方法也进行重写

public class Test {
    public static void main(String[] args) {
        // 对象 1
        Person p1 = new Person();
        p1.setName("您好");
        p1.setAge(18);
        // 对象 2
        Person p2 = new Person();
        p2.setName("您好");
        p2.setAge(18);
        // 创建 Set 集合
        Set<Person> set = new HashSet<Person>();
        set.add(p1);
        set.add(p2);
        // 打印 Set 中的所有数据
        set.forEach(p -> {
            System.out.println(p);
        });
    }
}
 
 
class Person {
    private String name;
    private int age;
 
    // 重写了 equals 方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true; // 引用相等返回 true
        // 如果等于 null,或者对象类型不同返回 false
        if (o == null || getClass() != o.getClass()) return false;
        // 强转为自定义 Person 类型
        Person person = (Person) o;
        // 如果 age 和 name 都相等,就返回 true
        return age == person.age &&
                Objects.equals(name, person.name);
    }
    
    // 重写了 hashCode 方法
    @Override
    public int hashCode() {
        // 对比 name 和 age 是否相等
        return Objects.hash(name, age);
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
     @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

        输出结果如下

        可以看出,如果只重写了 equals ()方法,那么Set 集合进行去重操作时,会先判断两个对象的 hashCode 是否相同,此时因为没有重写 hashCode 方法,所以会直接执行 Object 类中的 hashCode() 方法,而 Object 类中的 hashCode ()方法对比的是两个不同引用地址的对象,所以结果是 false,那么 equals ()方法就不用执行了,直接返回的结果就是 false:两个对象不是相等的,于是就在 Set 集合中插入了两个相同的对象。

        但是,如果在重写 equals ()方法时,也重写了 hashCode ()方法,那么在执行判断时会去执行重写的 hashCode ()方法,此时对比的是两个对象的所有属性的 hashCode 是否相同,于是调用 hashCode 返回的结果就是 true,再去调用 equals ()方法,发现两个对象确实是相等的,于是就返回 true ,因此 Set 集合就不会存储两个一模一样的数据了。

        综上所述,当我们同时重写了两个方法之后,Set 集合成功进行了去重的操作,代表此时两个对象是完全相等的。

  • 46
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值