首先对这三种操作符分别进行解释说明。
1. ==
==可以操作两种数据,一种是数值类型,一种是引用类型。
- 数值类型
顾名思义,对数值类型使用“==”就是对两个数据的值进行比较,即使这两个数据是不同的类型,只要他们的值相等,“==”的结果就是相等的。
public static void main(String[] args) {
System.out.println(97 == 'a'); //true
System.out.println(97 == 97.0000); //true
}
- 引用类型
对引用类型的比较,并不是比较其值,而是比较其引用的对象在内存中的地址是否相同。
public static void main(String[] args) {
String a = new String("aaaaaa");
String b = new String("aaaaaa");
System.out.println(a == b); //false
}
因为a与b是两个不同的对象,他们在内存中的地址也不相同,所以结果为false。
- 小结
- 对于两个数值类型使用“==”,就是对其值进行比较,即使其类型不同也没有关系。
- 对于两个引用类型而言,比较的是其引用的对象在内存中的位置,如果位置相同则为true。即这两个引用类型引用的是同一个对象。
2. equals
equals是Object的实例方法,用于比较其content是否相同。
首先对于默认的Object的equals方法而言,其源码为:
public boolean equals(Object obj) {
return (this == obj);
}
从中可以看出来,其效果与使用“==”是相同的,即为比较两个对象在内存中的地址是否相同。
然而对于继承Object的类来说,它们重写了equals方法。以String类为例:
public static void main(String[] args) {
String a = new String("aaaaaa");
String b = new String("aaaaaa");
System.out.println(a.equals(b)); //true
}
如果按照Object类中的equals方法来进行比较的话,应该是true才对,但实际结果为false,这就是因为String重写了equals方法:
public boolean equals(Object anObject) {
if (this == anObject) { //对象在内存中地址相同时返回true
return true;
}
if (anObject instanceof String) { //对象是否为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) { //对String中的char进行比较
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
从上述源码看出,String的equals实现首先是使用“==”进行比较,如果是同一个对象则返回true,如果不是相同的对象,则判断是否是同一类型,相同的话则对String的值(即组成String的char)进行比较。java中所有内置类的equals方法均是这样实现的,例如Integer等。
3. hashCode
hashCode在Object类中的实现是将对象在内存中的地址转化为一个int类型。
然而在实际应用中,Object的子类会对这个方法进行重写,以满足各自的需求,下面还是以String类为例。
我们有一个统计世界上所有人名的需求,将其放入一个set中,而set是不能够有重复数据的,那我们怎么判断新加入的数据是否和set中已有的数据相同呢?可能有人会说使用重写的equals方法,但是假如set中已经有几十万的数据了,那每添加一次数据,就要和之前存在的几十万数据进行equals比较,这就太浪费时间和计算资源了。
这个时候就需要hashCode的帮助了,hashCode能够通过一个特定的算法对每一个对象得到一个特定的值,这个值重复的概率很低。
因此我们对每一个加入到set中的数据都求出其hashCode,然后把这个hashCode作为其在set中的位置。这样基本上每一个不同的数据都能够找到其在set中的位置,而且因为相同的对象使用hashCode方法得到的值是一样的,这就保证set中不会存在相同的数据了。
hashCode的源码如下所示:
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
从中可以看出,该值是使用s[0]31^(n-1) + s[1]31^(n-2) + ... + s[n-1]这个算法得到的,而这个算法所需要的数据,如val[i], value.length等均是equals方法实现中所需要的数据,可以这样说hashCode方法就是使用equals方法中的数据进行特定的算法运算后得到的值。
这样一来,hashCode与equals方法的关系就很明显了,在添加数据到set这种拥有不重复数据的集合中时,使用equals方法需要进行大量的计算,而hashCode就是帮助处理这种情况的。因为其内部实现使用了相同的数据,只是算法不同,所以:
- equals方法返回“true”,其hashCode方法返回的值也是相同的。
- equals方法返回“false”,其hashCode方法返回的值不一定不同,因为其内部实现的算法不能够保证输入值不同,输出值也不同。例如1 + 1 = 2 , 0 + 2 = 2。
- hashCode返回的值不同,其equals方法返回的一定是“false”
4. 总结
- hashcode是系统用来快速检索对象而使用
- equals方法本意是用来判断引用的对象是否一致
- 重写equals方法和hashcode方法时,equals方法中用到的成员变量也必定会在hashcode方法中用到,只不过前者作为比较项,后者作为生成摘要的信息项,本质上所用到的数据是一样的,从而保证二者的一致性。