在学习Java时,我们知道 == 是用来比较数值是否相等,而equals则是用来比较字符串之间是否相等。那 == 和 equals 究竟比较的是什么呢?
==比较
比较的是对象的引用
Java的基本类型直接存储“值”并放在栈内存中
所以我们在比较基本类型时,使用==比较数值是否相等是可行的
equals比较
在Object类中equals实现的也是比较对象的引用。 Object中equals源码如下,更多请查看equals文档
public boolean equals(Object obj) {
return (this == obj);
}
那为什么在比较字符串时,我们能得到我们想要的结果呢?
是因为String类重写了equals方法,String的equals源码如下
/**
* Compares this string to the specified object. The result is {@code
* true} if and only if the argument is not {@code null} and is a {@code
* String} object that represents the same sequence of characters as this
* object.
*
* @param anObject
* The object to compare this {@code String} against
*
* @return {@code true} if the given object represents a {@code String}
* equivalent to this string, {@code false} otherwise
*
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
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;
}
相同的,其他的封装类也实现了equals的重写
String的比较
接下来我们来测试一下字符串的比较
public class Test{
public static void main(String[] args) throws ParseException {
String strA = new String("abc");
String strB = new String("abc");
System.out.println("strA==strB? "+(strA==strB)); //strA==strB? false
System.out.println("strA.equals(strB)? "+strA.equals(strB)); //strA.equals(strB)? true
System.out.println("strA hashCode:"+strA.hashCode()); //strA hashCode:96354
System.out.println("strB hashCode:"+strB.hashCode()); //strB hashCode:96354
String strC = "abc";
String strD = "abc";
System.out.println("strC==strD? "+(strC==strD)); //strC==strD? true
System.out.println("strC.equals(strD)? "+strC.equals(strD)); //strC.equals(strD)? true
System.out.println("strC hashCode:"+strC.hashCode()); //strC hashCode:96354
System.out.println("strD hashCode:"+strD.hashCode()); //strD hashCode:96354
System.out.println("strA==strC? "+(strA==strC)); //strA==strC? false
System.out.println("strA.equals(strC)? "+strA.equals(strC)); //strA.equals(strC)? true
}
}
可以观察到,当我们使用new String()生成的字符串时,两个对象的引用是不一样的。而直接使用字面量赋值给String变量时,两者的引用则相同
这是因为虚拟机会将确定的字符串常量池化到常量池中,所以在运行该main()方法之前,字符串abc已经在字符串常量池中了。当我们使用new创建一个新的String对象时,如果常量池中有该字符串,有则返回对应的引用给在堆中新建的String对象;没有则在常量池中创建对应的字符串,并将指向该字符串的引用给在堆中新建的String对象。而如果直接使用字面量赋值,则直接返回字符串在常量池中的引用。
因此strA和strB都是各自指向它们在堆中的对象的引用,但它们各自的对象指向同一个字符串引用。而strC和strD都是指向常量池中abc的引用
深入理解请看以下文章
String你真的会吗?不会还不进来!!!是等我胖虎锤各位呢?!!!
在上述代码中,我们可以观察到strA strB strC strD的hashCode是一致的。
在此之前我认为它们的引用的哈希值应该是引用的所指的内存地址。注意,这是错误的。
查看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;
}
可以看出,哈希值是通过公式h = 31 * h + val[i]计算的,所以当我们传入相同的字符串时,得到的哈希值相同
Integer的比较
public class Test{
public static void main(String[] args) throws ParseException {
int a = 1;
int b = 1;
System.out.println(a==b); //true
Integer objectA = 2;
Integer objectB = 2;
System.out.println(objectA==objectB); //true
System.out.println(objectA.equals(objectB)); //true
}
}
为什么此处的两个Integer对象使用==比较,会得到true的结果呢?
这是因为Integer 内部维护着一个 IntegerCache 的缓存,默认缓存范围是 [-128, 127],所以 [-128, 127] 之间的值用 == 和 != 比较也能能到正确的结果,但是不推荐用关系运算符比较。
具体见JDK中的 Integer 类源码
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}