java equals() 和 hashCode()学习

1.深入理解 equals() :

即比较两个对象的内容是否相等,没有重写 Object的 euqals()时结果如下:

public class TestEquals {
  public static void main(String[] args) {
    
    Cat c1 = new Cat(1, 1, 1);
    Cat c2 = new Cat(1, 1, 1);
    
    System.out.println("c1==c2的结果是:"+(c1==c2));					//false
    System.out.println("c1.equals(c2)的结果是:"+c1.equals(c2));		//false
  }
}
 
class Cat {
  int color, weight, height;
 
  public Cat(int color, int weight, int height) {
    this.color = color;
    this.weight = weight;
    this.height = height;
  }
}

Object 类中 的equals:比较的时地址

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

所有我们要在自定义类中 重写 equals() 来比较内容,在自定义类加入如下代码。

public boolean equals(Object obj){
    if (obj==null){
      return false;
    }
    else{
      if (obj instanceof Cat){
        Cat c = (Cat)obj;
        if (c.color==this.color && c.weight==this.weight && c.height==this.height){
          return true;
        }
      }
    }
    return false;
}

2.Hash算法原理以及 HashCode()深入理解

Returns a hash code value for the object. This method is supported for the benefit of hash tables uch as those 
provided by java.util.HashMap.

Whenever it is invoked on the same object more than once during an execution of a Java application,
the hashCode method must consistently return the same integer, provided no information used in 
equals comparisons on the object is modified. This integer need not remain consistent from one execution of
 an application to another execution of the same application.

If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of 
the two objects must produce the same integer result.It is not required that if two objects are unequal 
according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce 
distinct integer results. 

As much as is reasonably practical, the hashCode method defined by class Object does return
distinct integers for distinct objects. (This is typically implemented by converting the internal address
of the object into an integer, but this implementation technique is not required by the Java™ programming language.)

上是 Object类中 hashCode()方法的注释,翻译如下:

返回对象的哈希码值。支持此方法是为了有利于哈希表,例如由 java.util.HashMap 提供的哈希表。

在 Java 应用程序执行期间,只要在同一个对象上多次调用它,hashCode 方法必须始终返回相同的整数,前提是在对象的 equals ()
比较中使用的信息没有被修改。该整数不需要从应用程序的一次执行到同一应用程序的另一次执行保持一致。

如果根据 equals(Object) 方法两个对象相等,则对两个对象中的每一个调用 hashCode() 方法必须产生相同的整数结果。
如果根据 equals(Object) 方法两个对象不相等,hashCode() 可能产生相等的整数结果。

 尽可能实用,类 Object 定义的 hashCode 方法确实为不同的对象返回不同的整数。 
(这通常通过将对象的内部地址转换为整数来实现,但 Java™ 编程语言不需要这种实现技术。)

总结下来就是 equals()相等则HashCode()必须相等,equals()重写则HashCode()必须重写,equals()不等HashCode()可以相等。
equals是判断两个对象的内容,hashcode也是基于内容判断的,但不是专注于内容。Object .hashCode() 返回的是对象地址的整数。

接下来内容就是转载自:http://blog.csdn.net/jiangwei0910410003/article/details/22739953博客*********************************************************************

  • 下面来看一下一个具体的例子: RectObject对象:
package com.weijia.demo;  
 
public class RectObject {  
    public int x;  
    public int y;  
    public RectObject(int x,int y){  
        this.x = x;  
        this.y = y;  
    }  
    @Override  						//我们自己手写的 hashCode,基于内容
    public int hashCode(){  
        final int prime = 31;  
        int result = 1;  
        result = prime * result + x;  
        result = prime * result + y;  
        return result;  
    }  
    
    /*
	@Override						//IDEA帮我们生成的 hashCode,基于内容,把对象属性当形参传进去
    public int hashCode() {
        return Objects.hash(name, age);
    }
    */
    
    @Override  
    public boolean equals(Object obj){  
        if(this == obj)  
            return true;  
        if(obj == null)  
            return false;  
        if(getClass() != obj.getClass())  
            return false;  
        final RectObject other = (RectObject)obj;  
        if(x != other.x){  
            return false;  
        }  
        if(y != other.y){  
            return false;  
        }  
        return true;  
    }  
} 
  • 我们重写了父类Object中的hashCode和equals方法,看到hashCode和equals方法中,如果两个RectObject对象的x,y值相等的话他们的 hashCode值是相等的,可见重写的 hashCode是基于内容的,同时equals返回的是true;

下面是测试代码:

package com.weijia.demo;  
import java.util.HashSet;  
public class Demo {  
    public static void main(String[] args){  
        HashSet<RectObject> set = new HashSet<RectObject>();  
        RectObject r1 = new RectObject(3,3);  
        RectObject r2 = new RectObject(5,5);  
        RectObject r3 = new RectObject(3,3);  
        
        set.add(r1);  
        set.add(r2);  
        set.add(r3);  
        set.add(r1);  
        System.out.println("size:"+set.size());  
    }  
}
  • 我们向HashSet中存入到了四个对象,打印set集合的大小,结果是多少呢? 运行结果:size:2
    因为我们重写了 hashCode方法,只要 x,y属性值相等那么对象的 hashCode值也是相等的,所以先比较hashCode的值,r1和r2对象的x,y属性值不等,所以他们的hashCode不相同的,所以 r2可以放进去,但是 r3的 x,y属性值和r1对象的属性值相同的,所以hashCode是相等的,这时候在比较 r1和 r3的equals方法(在链表中),因为他么两的x,y值是相等的,所以r1,r3对象是相等的,所以r3不能放进去了,同样最后再添加一个r1也是没有没有添加进去的,所以set集合中只有一个r1和r2这两个对象。

  • 下面我们把 RectObject对象中的 hashCode方法注释,即采用的是Object的返回地址的hashCode ,在运行一下代码:
    运行结果:size:3
    这个结果也是很简单的,r1, r2. r3是不同的对象地址不同,hashCode都不一样,存放到三条不同的链表中,而再次放入r1时,里面的 r1同要放入的 r1的 hashCode和 equal都一样,放入失败。

  • 下面我们把RectObject对象中的 equals方法中的内容注释,直接返回false,不注释hashCode方法,运行一下代码:
    运行结果:size:3 这个结果就有点意外了,我们来分析一下:
    r1, r2 hashCode不一样,存放到2条不同的链表中,放人 r3时,r1、r3的hashCode一样,equal不一样(始终返回false),可以放入。而再次放入r1时,里面的 r1同要放入的 r1的 hashCode一样、equal不一样(始终返回false),按理说应该放入成功,为什么失败了呢?看set放入源码:

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

这里我们可以看到其实HashSet是基于HashMap实现的,我们在点击HashMap的put方法,源码如下:
由于两个key可能是哈希碰撞而不是真的(内容相等、地址相等),所以在哈希值一样的前提下还需要对key自身验证。这样才能真的确定set里面是否已经含有了该key。

    public V put(K key, V value) {
        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;
            // 前提是哈希值一样。
            // 即 key.equals(k)为true 或者 key地址一样,都认为map中已经有该key了。
            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;
    }

首先是判断 hash 值是否相等,不相等的话就说明不可能是同一个key,直接跳过。相等的话,由于可能是哈希碰撞,所以要在hash值相等的前提下在 链表中 验证是否是同一个key,从key的 地址内容 两个角度都行。

set、map中寻找key的过程,首先通过 key.hashcode() 计算出key的hashcode,然后通过hashcode计算出hash值,再通过hash值计算出桶下标。key -> hashcode -> hash -> index

最后我们在来看一下hashCode造成的内存泄露的问题:看一下代码:

package com.weijia.demo;
import java.util.HashSet;
public class Demo {
    public static void main(String[] args){
        HashSet<RectObject> set = new HashSet<RectObject>();
        RectObject r1 = new RectObject(3,3);
        RectObject r2 = new RectObject(5,5);
        RectObject r3 = new RectObject(6,6);
        
        set.add(r1);
        set.add(r2);
        set.add(r3);
        r3.y = 7;
        System.out.println("删除前的大小size:"+set.size());
        set.remove(r3);
        System.out.println("删除后的大小size:"+set.size());
    }
}
  • 运行结果:
    删除前的大小size:3
    删除后的大小size:3
    根据对象 r3找不到它在以前存放在 set中的引用了(找不到引用所在的链表,即 hashCode变了)

擦,发现一个问题了,而且是个大问题呀,我们调用了remove删除r3对象,以为删除了r3,但事实上并没有删除,这就叫做内存泄露,就是不用的对象但是他还在内存中(set中无法删除的引用一直维护)。所以我们多次这样操作之后,内存就爆了。

因为 HashMap 在调用remove方法的时候,会先使用对象的 hashCode值去找到对应链表,然后通过key的(地址或equals)找到存在的对象,我们修改属性导致 hashCode变化,使得链表就找错了,所以remove方法中并没有找到r3了,所以删除失败。所以存入数组的对象不应再做修改。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值