Java的equals方法和hashCode方法

  今天在写程序的时候遇到一个问题,问题的简化描述如下:

  我们设计了一个类Point用来存储地名和相应的经纬度,相关代码如下:

 1 import java.util.HashSet;
 2 import java.util.Set;
 3 
 4 public class Point {
 5     private String addr;
 6     private int latitude;
 7     private int longitude;
 8 
 9     public Point(String a, int la, int lo) {
10         this.addr = new String(a);
11         this.latitude = la;
12         this.longitude = lo;
13     }
14 
15     @Override
16     public boolean equals(Object obj) {
17         if (!(obj instanceof Point)) {
18             return false;
19         } else {
20             if (obj == this) {
21                 return true;
22             } else {
23                 Point p = (Point) obj;
24                 return addr.equals(p.addr) && latitude == p.latitude && longitude == p.longitude;
25             }
26         }
27     }
28 
29     @Override
30     public String toString() {
31         return new String(addr + ":(" + latitude + "," + longitude + ")");
32     }
33 
34     public static void main(String[] args) {
35         Point p1 = new Point("Beijing", 40, 116);
36         Point p2 = new Point("Beijing", 40, 116);
37         Set<Point> hashset = new HashSet<>();
38         hashset.add(p1);
39         hashset.add(p2);
40         System.out.println(hashset);
41         System.out.println(hashset.contains(new Point("Beijing", 40, 116)));
42     }
43 }

  这时候,程序的输出是:

1 [Beijing:(40,116), Beijing:(40,116)]
2 false

  这个结果显然与Set的不含重复元素的属性不符合,并且调用Set的contains得到的结果也与我们期望的完全不符。参考HashSet的java docs,里面提到了:public boolean contains(Object o)  Returns true if this set contains the specified element.More formally, returns true if and only if this set contains an element e such that (o == null ? e == null : o.equals(e))。此外,关于add方法,java docs中的描述也是对于一个待添加的元素E e, 当集合中找不到一个元素e2使得(e == null ? e2 == null : e2.equals(e))时才将e添加到集合中并返回true,否则原集合保持不变并返回false。因此,HashSet中用来判断两个元素的方法是调用描述元素e的类的equals方法,即比较的是元素的值而不是元素的引用地址。但在上面的代码中,我们已经重写了Point的equals方法,使得判断两个Point实例是否相等是根据Point封装的内部的值来判断的,但是为什么程序还是给出了错误的结果呢?

  为了了解HashSet的实现的原理,我查看了jdk中HashSet的源码,HashSet实际上是由HashMap实现的,所以查看HashMap的源码。以HashMap<K,V>的containskey(Object o)方法为例,首先要通过hash方法计算出K key的hash值,然后通过这个hash值定位到存储这个键值的位置,然后比较这个位置上存储的K元素是否与key相等(通过equals方法)。因此,Set的contains方法(调用的其实就是HashMap的containsKey方法)并不是把集合中所有的元素都与待比较元素都用equals方法比较一遍,而是只对hash值定位到的元素调用equals方法。hash值的计算需要调用对象的hashCode方法。在上面的代码中,我们并没有对hashCode进行重写,因此有可能造成值相同的元素却对应不同的hash值。

  此外,上面的程序在设计时没有考虑到equals()方法和hashCode()方法之间的设计实现原则。关于equals()方法和hashCode()方法的紧密联系,以下参考了Java中Set的contains方法这篇文章(点击链接可阅读原文)。这两个方法的设计实现原则为:

  如果两个对象相等(使用equals()方法),那么必须拥有相同的哈希码(使用hashCode()方法).
    即使两个对象有相同的哈希值(hash code),他们不一定相等.意思就是: 多个不同的对象,可以返回同一个hash值.

  因此,我们在重写equals()方法时,最好将hashCode()也进行重写。上面的代码加上以下代码后,就能得到期望的结果。

1 @Override
2 public int hashCode() {
3     String s = new String(addr + latitude + longitude);
4     return s.hashCode();
5 }

  

 

转载于:https://www.cnblogs.com/njufl/p/Java.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值