java基础:equals() 和 hashCode()的一些理解


Object类中

equals()hashCode()方法都是Object类中的方法(其他类都是从这个方法中继承而来的),不过hashCode()native函数不开源(native函数为本地函数和平台相关,一般都是用C来实现的,已经与Java语言没什么关系)

equals()

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

一看就知道是判断两个对象的地址(即引用)是否相等。但是我们必需清楚,当StringMath、还有IntegerDouble……等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法。

比如String类的equals():

public boolean equals(Object anObject) {
        if (this == anObject) {//如果两个引用所指向同一地址,那自然是内容也相等。
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String) anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                            return false;
                    i++;
                }
                return true;
            }
        }
        return false;
}	// 很明显,这是对String对象中的内容进行比较
Java语言中对equals()的要求
  • 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”

  • 反射性:x.equals(x)必须返回是“true”

  • 类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”

  • 还有一致性:如果x.equals(y)返回是“true”,只要xy内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”

  • 任何情况下,x.equals(null),永远返回是“false”x.equals(x不同类型的对象)永远返回是“false”

    以上这五点是重写equals()方法时,必须遵守的准则

hashCode()

public native int hashCode();

是与平台相关的native函数,返回值为:这个对象的哈兮地址

hashcode方法在把对象放到一个对象容器时大派用场,一个好的hashcode算法和坏的算法,在把对象放入容器和从容器取出时,效率相差极大

看不到Object类的hashCode()实现,那我们看一下String类重写的hashCode()的实现:

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {//value数组里面放的是String对象的字符
            char val[] = value;
		//int算法 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]  
		// 就是用这个算法来计算String的hash地址的
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
}// @return  a hash code value for this object.

equals()hashCode()的关系

  • equals()相等的对象hashCode()一定相等(物理地址都确定相等,肯定是一个对象了)

  • hashCode()相等的对象,equals()相等或不等(你还记得散列表,采用直接寻址技术,在理想状态下无须任何比较就可以找到待查关键字,查找的期望时间为o(1)。有的时候hash地址相同,但发现这个地址有元素后就会去比较和这个地址中的元素是否equals,不equals的话就要再散列找个地方存放对象。hash地址一般并不是对象存储地址,而是一个可以映射到对象物理地址的地址

  • equals()不相等的对象,hashCode()有可能相等(原因的话就是和上面一样,由哈兮冲突导致)

  • hashCode()不相等的对象,equals()一定不相等我们可以这要理解问题:哈兮码是根据关键码值(或者说对象的某些特征)来计算的来的。你想哦,比较两个对象的哈兮码,如果相同(我们可以理解为这两个对象的某些特征是相同的,但你能保证其他特征也相同吗?,如果不同(我们可以理解我,这两个对象连某些特定的特征都不相同,那这两个对象肯定不相同了)

一个实例
public static void main(String[] args) {
		// TODO Auto-generated method stub
		String s1=new String("zhangjinyu");
		String s2=new String("zhangjinyu");
		System.out.println("s1==s2:"+(s1==s2));
		System.out.println("s1.equals(s2):"+s1.equals(s2));
		System.out.println("s1.hashCode()==s2.hashCode():"+(s1.hashCode()==s2.hashCode()));
		System.out.println("s1.hashCode():"+s1.hashCode());// String类重写了hashCode()
		System.out.println("s2.hashCode():"+s2.hashCode());
		Set hash_set=new HashSet();
		hash_set.add(s1);
		hash_set.add(s2);
		System.out.println(hash_set);
		System.out.println("hash_set.hashCode():"+hash_set.hashCode());// hash_set运行的hashCode()是AbstractSet中实现的
	}
运行结果:
(一)hash_set中只有一个对象
s1==s2:false
s1.equals(s2):true
s1.hashCode()==s2.hashCode():true
s1.hashCode():1943454207
s2.hashCode():1943454207
[zhangjinyu]
hash_set.hashCode():1943454207
(二)hash_set中有两个不同的对象:s2  zhangjinyu → zhangjin
s1==s2:false
s1.equals(s2):false
s1.hashCode()==s2.hashCode():false
s1.hashCode():1943454207
s2.hashCode():-1432612957
[zhangjinyu, zhangjin]
hash_set.hashCode():510841250

  来浅析一下为什么hash_set.hashCode()==s1.hashCode()==s2.hashCode()?
  答案: 我改了下s2,发现就不会相等了,原来是这样:HashMap中的hashCode的hash算法与他的每个元素有关,上面之所以相同是因为hash_set中只有一个元素
 // HashSet.class
  private transient HashMap<E,Object> map;
  private static final Object PRESENT = new Object();	// 额,是个常量,并且是final型的,这是jdk为了方便将value设为常量PRESET,因为value是可以重复的。
  public boolean add(E e) {
        return map.put(e, PRESENT)==null;	// 所以说,调用一下HashSet的add方法,实际上就是想HashMap中增加一行(key-value)记录;
    }
   // HashMap.class 
   public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        // 计算key对象的hash码
        // 用key的hash码的int值和table的长度(也就是key-value对数)进行“与”计算
        // 结果为这个可以对象在Entry[] table数组中的下标
        int hash = hash(key);	
        int i = indexFor(hash, table.length);	
        // 这个table有讲究,他是transient Entry<K,V>[] table;
        // 而Entry是实际上真正存放key-value对的地方
        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中的Key值的hash码 
    // hash方法就是我们在数据结构中讲的散列函数。它是经过放进HashSet里面的对象作为key得到hashCode码,在进行散列得到的一个整数。 	
    final int hash(Object k) {
        int h = 0;
        if (useAltHashing) {
            if (k instanceof String) {
                return sun.misc.Hashing.stringHash32((String) k);
            }
            h = hashSeed;
        }

        h ^= k.hashCode();	// k.hashCode()用k对象的hashCode()得到hash码

        // 这是一个散列算法
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    **** hash_set.hashCode() ****
	// 这里,说明hash_set.hashCode()的值为hash_set中每个对象的hashCode的和
    public int hashCode() {	
        int h = 0;
        Iterator<E> i = iterator();
        while (i.hasNext()) {
            E obj = i.next();
            if (obj != null)
                h += obj.hashCode();
        }
        return h;
    }
}



参考文献

ITeye作者zhaoxudonglove的文章《javahashcode()equals()的详解

作者L.G.AlexanderITeye中的文章《HashSetHashMap关系之源码分析,分析的相当认真,感谢这位作者的用心。







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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值