关于“==”
如果是原始类型,就是直接比较他们的值;
如果是引用类型,比较的就是引用的值。引用的值就是指对象的地址。也就是两个引用指向同一个对象,那么==比较返回true,否则false。
这里需要说明的一个问题是,关于Integer类中一个看起来奇怪的现象。
看int的包装类型Integer类:
Integer a = 20;
Integer b = 20;
Integer c = 200;
Integer d = 200;
int e = 200;
System.out.println(a == b);
System.out.println(c == d);
System.out.println(d == e);
输出结果:
true
false
true
原来在Integer类的valueOf方法中默认缓存了-128到127之间的数字。而且在Integer与int进行比较的时候会进行自动拆箱操作,也就是比较他们的数字值。
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
我们可以在JVM启动时设置参数-Djava.lang.Integer.IntegerCache.high=1000来间接设置IntegerCache。high的值,或者通过设置-XX:AutoBoxCacheMax=<size>。那么对于其他原始类型的包装类,是否有同样的情况呢。经过研究发现,这些类型的valueOf方法实现如下:
- Boolean的两个值true和false都是缓存在内存中的,无需做任何改造,自己new出来的则是另外一个堆内存。
- Byte的256个值全部是缓存在内存中的,自己new出来的则是另外一个堆内存。
- Short和Long两种类型的缓存范围为-128-127,无法调整缓存范围,在实际场景中需要开发者可以根据需要自己做缓存。
- Float,Double没有缓存,在实际场景中需要开发者可以根据需要自己做缓存。
关于equals()方法
equals()方法首先在Object类中提供了默认实现:
public boolean equals(Object obj) {
return (this == obj);
}
如果不去重写equals()方法,并且在父类列表中都没有重写过equals()方法,那么equals()方法默认就是比较对象地址。一般equals()方法是希望子类去重写的,以实现对比值的功能。
JDK8中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;
}
-
首先判定对比的对象和当前对象地址。如果相同直接返回true;
-
如果不相同,判定出入对象是否为String,若不是直接返回false;
- 如果为String类型,判断字符串长度,若不一致直接返回false;
- 如果长度相同,循环对比两个字符串的char[]数组。逐个对比字符是否相同,若存在不一致的情况就返回false。
关于hashCode()方法
一般equals()方法是与hashCode()方法成对出现的。hashCode()方法提供了对象的hashCode值,是一个native方法:它的返回值默认是与System.identityHashCode(obj)方法相同。注意,它的值绝对不是代表对象的地址。
public native int hashCode();
hashCode只能说是标志对象,这样在hash算法中可以将对象相对离散开,这样就可以在查找数据时根据key快速缩小范围。但我们不能说hashCode值一定是唯一的(不同对象实例可能有相同的hashCode值,这个很好理解,子类可以重写hashCode()方法,当然有可能导致这个结果。)。因此在hash算法应用中在定位到具体链表后,需要进一步循环链表,然后通过equals方法来对比一下key值是否一样。
前面说到hashCode()方法是可以重写的,重写后的方法将决定它在hash相关数据结构中分布情况,通常我们尽可能将对象相对离散开。
下面看看JDK8中String类的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;
}
从注释中我们知道String对象的hashCode计算算法为:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
其中s[i]为字符串中对应位置字符,n为字符串长度。这个hashCode是根据char[]数组计算出来的,不同的String完全可以算出相同的hashCode值。因此即使hashCode值一样,也不能证明两个String是一样的。还需要再次遍历char[]数组对比一次才能证明两个对象是否一样。