深入String存储----区别equals与==

对于 equals与== 想必大家都非常熟悉,说开点,所有类的父类Object,里面就有equals 方法,里面的实现就是==。

不过,对象有三大特性,封装,继承,多态,许多类继承Object 类之后,根据需要,封装了不同的属性,自然也不能直接进行==,这样只是比较地址,毫无意义,于是各种子类都有了自己的实现,比如:Date类中的equals 比较的是时间,HashMap中Node<K,V>的equals 比较地址,同时也比较值,任意一个相同就返回true。

当然,我们平时遇到比较多的还是String中的equals和==。不过说句实在话,代码中谁用== 啊,都是用equals,没啥说的,安全!

让我们来看一下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;
    }

这个代码倒是没什么难度,就是先比较两个字符串是不是同一个,比较地址,然后就是比较String中value数组是否相同。两个条件只要有一个成立,equals就返回true。

这个其实大家都知道,写代码也不会在String中的equals这里出现bug,为啥这么多人都热衷于区分equals与==呢?面试经常问呗,不然谁去刨根问底啊,闲的慌啊?

最初级的菜鸟遇到的估计就是什么String中equals与==,有什么区别,可以互换么?问什么?源码中很容易看出来答案、

然后成长了一点,就来一个判断几个String 的== 是true还是false。

在之后,面试的开胃小菜中可能会碰到关于String存储之类的问题。

下面就来说一说String存储和相等的判断。

大家都知道JMM底层的存储结构,简单的分一下,就是堆内存,栈内存,方法区,常量池,寄存器之类的。今天我们针对的是String,所以我们主要研究的就是堆内存,栈内存,与常量池!

其实常量池也是在堆内存中开辟的一段区域,数据的存储主要还是在堆内存。

我们来看一下String创建存储的过程。

String str = new String("abc");

新建一个String对象,并完成赋值abc,那它经历了怎样的创建过程呢?

首先,这个操作肯定会生成一个“abc”的字符串。内存栈中新建一个str 作为对象的引用。

接着,就是堆内存,创建一个值是“abc”的String对象。最后,就是检查常量池是否有“abc”字符串,如果没有,那么要新创建一个,如果有,则忽略。

这是一个简单的创建过程,那好,现在我们看一下具体例子:

如上图,创建了5个对象,我们一个一个看。

第一个,创建了一个对象的引用s1,在常量池中创建了一个“abc”,堆内存中应该也创建了一个对象,不过没有引用,下次GC会被回收。

第二个,创建了一个对象的引用s2,堆中创建了一个对象,常量池中已经有了“abc”,不在创建。

第三个,创建了一个对象的引用s3,堆中创建了创建了“a”,"bc"以及“abc”,不过“a”和“bc”并无引用,下次gc也会被回收,常量池中“abc”已经有了,不会创建,“a”,"bc"会被创建,但是也没有引用,不久后也会被回收。

第四个,创建了一个对象的引用s4,堆中创建了一个对象,常量池中已经有了“abc”,不在创建,然后使用intern()方法获取常量池中的字符串。

第五个,创建了一个对象的引用s5,堆中创建了一个对象,不过并未被引用,直接指向了常量池中的字符串。

很显然,s1与s5都是指向常量池中的“abc”,同时intern(),指向的就是“abc”这个字符串第一次创建的对象,是同一个数据,使用==结果也是true,

s2,s3,s4,都是一个对象,使用==结果一定是false。

可能有些小伙伴有点小迷糊,s4也是指向常量池中的“abc”啊,怎么和s1不能==呢?

好吧,大家来看下intern()的源码:

public native String intern()

嗯,只有这一行,是native方法,底层不是java实现的。不过没关系,从这一行中我们就可以找到我们想要的结果了。

我们看下s4的创建过程,首先是创建了一个s4的字符串,然后s4.intern(),那么这个s4.intern()和s4有个毛关系啊?只是一个常量池字符串的获取好不?而且还没有赋值。intern()这个方法明显是有返回值的好不?你这个返回值没有赋值,这句代码s4.intern()几乎就是废的好不,就是明确的说:我就来干扰你,咋地?

好吧,来运行下看看:

public class Test {
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = new String("abc");
        String s3 = new String("a")+ new String("bc");
        String s4 = new String("abc"); s4.intern();
        String s5 = new String("abc").intern();
        System.out.print("s1==s5的结果");
        System.out.println(s1==s5);
        System.out.print("s1==s2的结果");
        System.out.println(s1==s2);
        System.out.print("s1==s3的结果");
        System.out.println(s1==s3);
        System.out.print("s1==s4的结果");
        System.out.println(s1==s4);
        System.out.print("s3==s4的结果");
        System.out.println(s3==s4);
    }
}

结果和预料的当然一致,另外,不要看我写个描述和结果还分两行,那是用“+”号连接会先连接字符串,然后再对比,那啥子也别玩了,嘿嘿,我还是过了最菜的阶段,已经向高级一点的菜鸟进阶了哈,好,来看下结果:

com.example.demo.Test
s1==s5的结果true
s1==s2的结果false
s1==s3的结果false
s1==s4的结果false
s3==s4的结果false

Process finished with exit code 0

有兴趣的同学可以debug一下,你会发现,这个创建字符串都有一个地址的,具体如何,很容易看出来的哦~

好了今天就到这吧~

猜你喜欢

转载自blog.csdn.net/zsah2011/article/details/105421652