Java中==与equals的区别

526 阅读4分钟

在学习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对象。而如果直接使用字面量赋值,则直接返回字符串在常量池中的引用。

因此strAstrB都是各自指向它们在堆中的对象的引用,但它们各自的对象指向同一个字符串引用。而strCstrD都是指向常量池中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() {}
    }

参考

hashCode()方法源码分析

比较两个Integer,为什么必须使用equals?不用==

浅谈Java中的equals和==

Thinking in Java 第四章