相信出去面试的小伙伴们对hashCode和equals这两个方法都不陌生了,接下来我们就来谈谈关于==、hashCode和equals那些事。
这两个在我们代码中可以说是出场频率非常高的,一般在比较一些基本类型时我们会使用==,而在比较两个对象时一般我们会使用equals。我们都知道Object是Java对象的超类,它里面提供了hashCode和equals,如果我们的对象没有覆盖这两个方法,则默认会使用Object这两个方法来进行比较。这里我们只看equals()方法。
public boolean equals(Object paramObject){
return this == paramObject;
}
从源码中我们可以看到其实在当我们调用equals方法时,最终还是用的==(其实==和equals在比较对象时都是比较的引用地址,当然前提是对象没有覆盖equals方法)。那么是不是说明我们可以直接用==替代equals呢。我们来看一个例子:
public static void main(String[] args) {
Integer i1 = new Integer(10);
Integer i2 = new Integer(10);
System.out.println(i1==i2);
System.out.println(i1.equals(i2));
}
结果:
false
true
这是为什么呢?我们知道当我new一个对象时,就会在堆中分配一块内存,所以i1和i2是两个不同的应用地址,自然就是false。而Integer覆盖了equals方法,覆盖后比较的是对象的值,所以true,而一般来说对应值类型的对象我们通常只要比较它的值是否相等。同样的我们常用的String类型也同样覆盖了equals。
延伸:
public static void main(String[] args) {
Integer i1 = 10;
Integer i2 = 10;
System.out.println(i1==i2);
}
结果:
false
理论上来说i1和i2是两个不同的对象,那么i1应该和i2不同等才对。这主要是因为缓存的原因,我们看下源码:
public static Integer valueOf(int paramInt){
assert (IntegerCache.high >= 127);
if ((paramInt >= -128) && (paramInt <= IntegerCache.high))
return IntegerCache.cache[(paramInt + 128)];
return new Integer(paramInt);
}
当我们直接Integer i1 = 10时,会调用valueOf方法,而在-128和127之间会直接从缓存中获取对象,这样就是同一个对象了,所以返回ture。这也是面试中常被问到的,而且也有人确实踩过坑,所以建议一般我们使用基本常量int作为变量,同样的Long也有缓存。
首先说明一下JDK对equals(Object obj)和hashCode()这两个方法的定义和规范:在Java中任何一个对象都具备equals(Object obj)和hashCode()这两个方法,因为他们是在Object类中定义的。 equals(Object obj)方法用来判断两个对象是否“相同”,如果“相同”则返回true,否则返回false。 hashCode()方法返回一个int数,在Object类中的默认实现是“将该对象的内部地址转换成一个整数返回”。
如果两个对象的hashCode()不相等,则这两个对象通过equals则一定不相等,而两个对象的hashCode()相等,两个对象通过equals比较却不一定相等。所以一般在map集合中会先通过hashCode()比较,再通过equals来比较。比如我们常用的HashMap()。
final int hash(Object paramObject)
{
int i = 0;
if (this.useAltHashing) {
if ((paramObject instanceof String)) {
return Hashing.stringHash32((String)paramObject);
}
i = this.hashSeed;
}
i ^= paramObject.hashCode();
i ^= i >>> 20 ^ i >>> 12;
return i ^ i >>> 7 ^ i >>> 4;
}
public V put(K paramK, V paramV)
{
if (paramK == null)
return putForNullKey(paramV);
int i = hash(paramK);
int j = indexFor(i, this.table.length);
for (Entry localEntry = this.table[j]; localEntry != null; localEntry = localEntry.next)
{
Object localObject1;
if ((localEntry.hash == i) && (((localObject1 = localEntry.key) == paramK) || (paramK.equals(localObject1)))) {
Object localObject2 = localEntry.value;
localEntry.value = paramV;
localEntry.recordAccess(this);
return localObject2;
}
}
this.modCount += 1;
addEntry(i, paramK, paramV, j);
return null;
}
在进行put操作时,会先根据key进行重新hash计算找到数组的位置(可参考:HashMap),然后循环对比有没有相同的key,如果有就直接替换其对应值。试想如果每次新增一个元素都需要将所有的元素全部循环通过equals比较一遍效率是比较低的。而先通过hashCode()计算其hash值然后进行处理判断如果该区域没有对应的相同值说明不存在相同的key则直接插入,如果有相同的值再通过equals去比较,相同则直接替换其value。这样的话再通过get操作时也可以先通过key的hash值定位到数组位置,再通过equals获取value。
一般来说当我们覆盖了equals方法时,也要覆盖hashCode()方法,因为假如我们覆盖了equals方法而没有操作好会导致一种情况,当equals相对时而hashCode()比较却不相等,这就会导致我们在操作map或者一些集合时出现问题。具体的可以看看《Effective Java》书中关于equals和hashCode的内容。