java中重写equals一定要重写hashCode方法

java中重写equals一定要重写hashCode方法

原作者博客地址: Working With hashcode() and equals()

equals和hashCode方法
  • equals(Object obj): 由Object类提供,用来判断当前对象和obj是否相等。JDK中默认实现方式是基于内存地址:只有两个对象的内存地址相等,那么它们才相等
  • hashCode(): 该方法由Object类提供,返回一个整型数值代表了对象在内存的地址,对于每一个对象,hashCode()方法随机返回一个唯一的整型的数值
使用规则
  • 如果x.equals(y)==true,那么x.hashCode()==y.hashCode()也必须返回true

也就是说,如果重写了类中的equals方法,那么必须重写hashCode方法

实体类Ball.java:

public class Ball {

    private int id;
    private String color;

    public Ball(int id, String color) {
        this.id = id;
        this.color = color;
    }

    public int getId() {
        return id;
    }

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

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}
情景1: 直接使用Object默认实现的equals和hashCode方法

测试:

@Test
public void commonEquals(){
    Ball ball1 = new Ball(1, "red");
    Ball  ball2 = new Ball(1, "red");
    System.out.println("ball1 hashcode: "+ ball1.hashCode());
    System.out.println("ball2 hashcode: "+ ball2.hashCode());
    System.out.println("ball1.equals(ball2): "+ ball1.equals(ball2));
}

结果:

ball1 hashcode: 26947503
ball2 hashcode: 22527820
ball1.equals(ball2): false

很明显,不同的对象,hashcode不相等,ball1和ball2自然不相等

情景2: 重写equals方法,但不重写hashCode方法

重写equals方法:

@Override
public boolean equals(Object obj){
    if (this == obj)
        return true;
    if (obj == null || getClass() != obj.getClass())
        return false;
    Ball that = (Ball)obj;
    return id==that.getId();
}

结果:

ball1 hashcode: 30430942
ball2 hashcode: 16191201
ball1.equals(ball2): true

在equals方法中,我们使用id作为两个对象相等的条件

如果在ArrayList中使用只重写equals之后的Ball

@Test
public void equalsWithArrayList(){
    ArrayList<Ball> arrayList = new ArrayList<>();
    Ball ball1 = new Ball(1, "red");
    Ball  ball2 = new Ball(1, "red");
    arrayList.add(ball1);
    arrayList.add(ball2);
    System.out.println("arrayList size: "+ arrayList.size());
    System.out.println("arrayList contains Ball: "+ arrayList.contains(new Ball(1,"red")));
}

好像并没有发现什么, ArrayList中本来就允许重复元素

如果在HashMap中使用只重写之后的Ball

@Test
public void equalsWithHashMap(){
    HashMap<Ball, Integer> map = new HashMap<>();
    Ball ball1 = new Ball(1, "red");
    Ball  ball2 = new Ball(1, "red");
    map.put(ball1, 1);
    map.put(ball2,1);
    System.out.println("map size: "+ map.size());
    System.out.println("map contains Ball: "+ map.get(new Ball(1,"red")));
}

结果:

map size: 2
map contains Ball: null

讲道理,map的size应该是1,而且map.get(new Ball(1, "red"))应该返回1,什么情况?找到HashMap的put源码:

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

好吧,HashMap的put操作时,先是比较hashCode值是否相等,然后比较equals方法,如果hashCode不相等,则认为是addEntry操作。实际上,Ball没有重写hashCode方法,第一new的操作,都会在内存中分配不同的地址,所以map.size=2。

再看,map.get()源码方法:

//HashMap.get()源码
public V get(Object key) {
   if (key == null)
       return getForNullKey();
   Entry<K,V> entry = getEntry(key);

   return null == entry ? null : entry.getValue();
}

final Entry<K,V> getEntry(Object key) {
  if (size == 0) {
      return null;
  }

  int hash = (key == null) ? 0 : hash(key);
  for (Entry<K,V> e = table[indexFor(hash, table.length)];
       e != null;
       e = e.next) {
      Object k;
      if (e.hash == hash &&
          ((k = e.key) == key || (key != null && key.equals(k))))
          return e;
  }
  return null;
}

可以看到,取值的时候先计算对象的hashCode值,map.get(new Ball(1,"red")),匿名new Ball(1,”red”),之前没有重写hashCode方法,又会产生新的hashCode,所以取出的是null

重写hashCode方法
@Override
public int hashCode() {
    return id;
}

再次运行equalsWithHashMap测试用例,结果:

map size: 1
map contains Ball: 1

可以看到,这次map的size为1,表明加入的ball1和ball2为同一实例

同样也适用于HashSet,HashTable,或者其他以hashCode机制作为存储的数据结构

结论:在重写了equals方法后,强制要求重写hashCode方法

  • 如果两个对象相等,那么他们必须有相同的hashCode
  • 如果两个对象有相同的hashCode,并不意味着他们相等
  • 单独重写equals方法会让业务中使用哈希数据结构的数据失效,如HashMap,HashSet,HashTable…
    +
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值