search.png
关于我
menu.png
java review(12) == 和 equals 你真的懂吗

大家都知道,==是比较引用的地址,而equals是比较实例的具体成员,我们可以通过重载equals来实现自己的判断方法,但是 ==和equals其实是挺复杂的,请看下面这个示例:

    @Test
    public void test1() throws Exception {
        int a = 1;
        Integer b = a;

        // 1
        System.out.println("a == b is " + (a == b));
        System.out.println("b.equals(a) is " + b.equals(a));

        b = new Integer(1);
        // 2
        System.out.println("a == b is " + (a == b));
        System.out.println("b.equals(a) is " + b.equals(a));
    }

大家可能猜测 1应该都是true,而2应该是都是false true对吧。因为第一次b只是把引用指向了a,第二次则是重新实例化了对象。

然而结果却是:

a == b is true
b.equals(a) is true
a == b is true
b.equals(a) is true

这是为什么呢?接着看下个例子你就明白了,

    @Test
    public void test1() throws Exception {
        int a = 1;
        Integer b = a;

        // 1
        System.out.println("a == b is " + ((Integer)a == b));
        System.out.println("b.equals(a) is " + b.equals(a));

        b = new Integer(1);
        // 2
        System.out.println("a == b is " + ((Integer)a == b));
        System.out.println("b.equals(a) is " + b.equals(a));
    }

输出是:

a == b is true
b.equals(a) is true
a == b is false
b.equals(a) is true

==是比较引用的地址,而equals是比较实例的具体成员这句话确实没错,但是他却说的是比较对象的情况,

而比较基本类型时:

1 == 2
true == false

这样却是比较值,从深入理解来说可能还是比较地址,因为有所谓的常量池的存在。但我们可以简单的认为就是比较值了。

== 在比较基本类型和包装类型时会先把包装类型转成基本类型,然后再比较其值。

在第二片段代码中:
((Integer)a == b)就变成了比较对象,此时比较的便是地址。所以第一次 == 比较结果是true,第二次 ==比较时结果是false。

还是Integer,我们接着看下面这次比较:

    @Test
    public void test2() throws Exception {
        Integer a = new Integer(1);
        Integer b = Integer.valueOf(1);


        System.out.println("a == b is " + (a == b));
        System.out.println("b.equals(a) is " + b.equals(a));
    }

如果答案是 false、true你会吃惊吗?

首先第一点,a是实例化的对象,它不指向常量池。
其次 b 是 valueOf,valueOf则有一个缓存机制:

    /**
     * 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);
    }

所以b和a指向的并不是同一个实例。

接下来这个例子也有点挑战:

    @Test
    public void test3() throws Exception {
        Integer a = Integer.valueOf(128);
        Integer b = Integer.valueOf(128);


        System.out.println("a == b is " + (a == b));
        System.out.println("b.equals(a) is " + b.equals(a));
    }

valueOf都从缓存取,那么指向的地址应该都是一样的吧,然而,结果却是:

a == b is false
b.equals(a) is true

hhh,这是因为Integer缓存的范围是-127 -- 127
把128改成127就没错了:

    @Test
    public void test3() throws Exception {
        Integer a = Integer.valueOf(127);
        Integer b = Integer.valueOf(127);


        System.out.println("a == b is " + (a == b));
        System.out.println("b.equals(a) is " + b.equals(a));
    }

输出:

a == b is true
b.equals(a) is true

String 也是比较复杂的,但是如果你真的理解了,那么你对于java底层的理解也会更深一层,

先从简单的例子看起:

    @Test
    public void test4() throws Exception {
        String a = "123";
        String b = "12" + 3;
        String c = "12" + '3';
        String d = "12" + "3";

        System.out.println("a.equals(b) is " + a.equals(b));
        System.out.println("a == b is " + (a == b));
        System.out.println("a == c is " + (a == c));
        System.out.println("a == d is " + (a == d));
    }

挺好理解的,都是true,因为都是常量,计算完 之后去常量池找,常量池不存在则放入,存在则返回其引用。

然后就是一个复杂的例子:

    @Test
    public void test5() throws Exception {
        String a = "123";
        String b = new String("123");
        String c = b.intern();

        System.out.println("a.equals(b) is " + a.equals(b));

        System.out.println("a == b is " + (a == b));
        System.out.println("a == c is " + (a == c));
        System.out.println("b == c is " + (b == c));
    }

结果如下,你猜对了吗 》》

a.equals(b) is true
a == b is false
a == c is true
b == c is false

  1. a == b 为false很好理解,因为一个是常量引用,一个是实例化对象的引用,所以他们指向的值不同
  2. a == c 为true,是因为intern这个函数的机制决定的,它会返回常量池中对应字符串的引用,如果常量池中不存在该字符串,则会先将该字符串放入常量池,然后再返回其引用。
  3. b == c 为false 也和 2一个道理,就不细说了

以下是intern的源码注释:

    /**
     * Returns a canonical representation for the string object.
     * <p>
     * A pool of strings, initially empty, is maintained privately by the
     * class {@code String}.
     * <p>
     * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>
     * It follows that for any two strings {@code s} and {@code t},
     * {@code s.intern() == t.intern()} is {@code true}
     * if and only if {@code s.equals(t)} is {@code true}.
     * <p>
     * All literal strings and string-valued constant expressions are
     * interned. String literals are defined in section 3.10.5 of the
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * @return  a string that has the same contents as this string, but is
     *          guaranteed to be from a pool of unique strings.
     */
    public native String intern();

中文即是:

/ ** 
  * 返回字符串对象的规范表示形式。 
  * <p> 
  * 最初为空的字符串池由
  * 类{@code String}私有维护。 
  * <p> 调用intern方法时,如果池已经包含一个等于该字符串的字符串,
  * 则该字符串由{@link #equals(Object)}方法确定,则该字符串来自池返回。
  * 否则,将此{@code String}对象添加到 池中,并返回对此{@code String}对象的引用。 <p> 
  * 因此,对于任意两个字符串{@code s}和{@code t},
  * {@code s.intern()== t.intern()}为{@code true} 
  * 仅当{@code s.equals(t)}为{@code true}时。 
  * <p> 
  * 所有文字字符串和字符串值常量表达式都是内联的。字符串文字是在<cite> Java™语言规范</ cite>的第3.10.5节中定义的。 
  * 
  * @return 与该字符串具有相同内容的字符串,但是保证来自唯一字符串池。 
  * /

== 和 equals应该还有更好玩的地方,留着以后慢慢挖掘了..

版权声明

知识共享许可协议 本文章由作者“衡于墨”创作,转载请注明出处,未经允许禁止用于商业用途

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
发布时间:2019年11月29日 09:50:06

评论区#

还没有评论哦,期待您的评论!

关闭特效