Java中==、equals()、hashcode()三者的理解

时间:2022-08-20 18:51:35

转载请注明出处:http://blog.csdn.net/li0978/article/details/53519268
以前看了有关==、equals()、hashcode()这三者的区别和使用,当时感觉就这一点知识能够记住,最近用到这块内容脑海中只有一丝印象却不知具体如何,好记性不如赖笔头,遂再重新总结一下,以便日后信手拈来。

==

对于引用对象而言,比较两个对象引用的是否是同一个对象。比较是的两个引用对象的存储地址是否一样。
对于基本数据类型而言,比较的就是两个数据的值是否相等。

String a = "aaa";
String b = "aaa";
String c = new String("aaa");
System.out.println(a==b); //比较1
System.out.println(a==c); //比较2

结果是:
true
false
解释:
1.String a = “aaa”这种方式的时候java首先在内存中寻找”aaa”字符串,如果有,就把aaa的地址给它,如果没有则创建。因此比较1结果返回true;
2.String c = new String(“aaa”)这种方式不论内存中是否存在“aaa”都会开辟一个新的空间来存储c对象,比较2两个引用的地址是不一样的,因此返回false;

equals()

针对对象而言,equals()和==效果是一样的。但是有一个这样的需求:发新书的时候,两个同学都发了一本语文书,这时候我们可以说这两本语文书是一样的。这里的equals()比较的是两个对象的值。

String c = new string("aaa");
String d = new String("aaa");
System.out.println(c.equals(d));

结果是:
true
深入源码能够发现String类中equals()方法已经进行重写过了。
所以针对这种需求我们一般要重写对象的equals()方法,重写的目的就是判断两个对象的值等价。另外JDK中基本数据类型的包装类中equals()方法也已经重写过了,比较的时候都是比较的两个对象的值。
在重写equals方法时,要注意满足离散数学上的特性:

  • 自反性 :对任意引用值X,x.equals(x)的返回值一定为true;
  • 对称性: 对于任何引用值x,y,当且仅当y.equals(x)返回值为true时,x.equals(y)的返回值一定为true;
  • 传递性:如果x.equals(y)=true, y.equals(z)=true,则x.equals(z)=true;
  • 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变;
  • 非空性:任何非空的引用值X,x.equals(null)的返回值一定为false;

hashCode()

hashCode()返回的是一个hashCode(hash码),hash码主要用于散列对象作为KEY来标示这个对象的存储位置,功能类似索引一样能够快速查找提取对象。由此可知每个对象的hash码是唯一的(在对象的内存地址基础上经过特定算法返回一个hash码)。一般情况下我们是不需要重写hashcode()的,但是当类需要放在HashTable、HashMap、HashSet等hash结构的集合时候,我们如果重写equals()方法必须就重写hashcode()方法,因为在hash结构的集合中存储对象就是通过hash算法来散列对象的。假如这里有一些箱子,hash码可以看成每一个箱子指定的编码,每一个元对象就是根据箱子的编码存入每一个箱子的,所有的箱子加起来就是一个HashSet,HashMap,或 Hashtable对象。当我们想找某一个元对象的时候,我们必须先指定他所在那个箱子的hash码,才能找到那个箱子,然后根据对象的值从那个箱子中拿到这个对象。
所以得出的结论是:
在java集合中判断两个对象是否相同,先判断两个对象的hashcode是否相等,再采用equals()判断两个对象的值是否相等。即:

  • 如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。
  • 如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。

equals()和hashcode()重写

由上可知我们equals()和hashcode()的方法重写是成对的,重写equals()方法必须重写hashcode()方法。
equals()方法的重写规则:

  1. 使用instanceof操作符检查“实参是否为正确的类型”。
  2. 对于类中的每一个“关键域”,检查实参中的域与当前对象中对应的域值。
    [2.1]对于非float和double类型的原语类型域,使用==比较;
    [2.2]对于对象引用域,递归调用equals方法;
    [2.3]对于float域,使用Float.floatToIntBits(afloat)转换为int,再使用==比较;
    [2.4]对于double域,使用Double.doubleToLongBits(adouble) 转换为int,再使用==比较;
    [2.5]对于数组域,调用Arrays.equals方法。

hashcode()方法的重写规则:

  1. 把某个非零常数值,例如17,保存在int变量result中;
  2. 对于对象中每一个关键域f(指equals方法中考虑的每一个域):
    [2.1]boolean型,计算(f ? 0 : 1);
    [2.2]byte,char,short型,计算(int);
    [2.3]long型,计算(int) (f ^ (f>>>32));
    [2.4]float型,计算Float.floatToIntBits(afloat);
    [2.5]double型,计算Double.doubleToLongBits(adouble)得到一个long,再执行[2.3];
    [2.6]对象引用,递归调用它的hashCode方法;
    [2.7]数组域,对其中每个元素调用它的hashCode方法。
  3. 将上面计算得到的散列码保存到int变量c,然后执行 result=37*result+c;
  4. 返回result。
public class TestBean {
private int mInt;
private short mShort;
private char mChar;
private byte mByte;
private boolean mBoolean;
private long mLong;
private float mFloat;
private double mDouble;
private Object mObject;
private TestBean[] mArray;

@Override
public boolean equals(Object o) {
if (!(o instanceof TestBean))
return false;
TestBean tb = (TestBean) o;
return tb.mInt == mInt
&&tb.mShort == mShort
&& tb.mChar == mChar
&& tb.mByte == mByte
&& tb.mBoolean == mBoolean
&& tb.mLong == mLong
&& Float.floatToIntBits(tb.mFloat) == Float.floatToIntBits(mFloat)
&& Double.doubleToLongBits(tb.mDouble) == Double.doubleToLongBits(mDouble)
&& tb.mObject.equals(mObject)
&& Arrays.equals(mArray, tb.mArray);
}

@Override
public int hashCode() {
int result = 17;
result = 31 * result + mInt;
result = 31 * result + (int) mShort;
result = 31 * result + (int) mChar;
result = 31 * result + (int) mByte;
result = 31 * result + (mBoolean ? 0 : 1);
result = 31 * result + (int) (mLong ^ (mLong >>> 32));
result = 31 * result + Float.floatToIntBits(mFloat);
long doubleToLong = Double.doubleToLongBits(mDouble);
result = 31 * result + (int) (doubleToLong ^ (doubleToLong >>> 32));
result = 31 * result + mObject.hashCode();
for (int i = 0; i < mArray.length; i++) {
result = 31 * result + mArray[i].hashCode();
}
return result;
}
}

这里的31和17仅仅作为一个因子,其实取什么值都可以的,hashcode是一个对象存储位置的标示,因子取值的标准就是最终的得到的结果要尽可能的分散。至于为什么都是取的31,这里大概有这几个原因:

  • 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!。(减少冲突)
  • 31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化.(提高算法效率)
  • 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)
  • 并且31只占用5bits,相乘造成数据溢出的概率较小。