Java字符串比较,相等 or 不相等?

环境

java8

正文

1.字符串常量池

相信不少小伙伴都遇到过这样的面试题,但真正了解背后的原理吗?
下面我们来看一下题目

    String s1 = "张三";
    String s2 = "张三";
    String s3 = new String("张三");
    System.out.println(s1 == s2); //true
    System.out.println(s1 == s3); //false

答案很简单,直接给出了答案,此时内存中的关系可以如图表示:

image.png

s1代码执行的时候会先查看字符串常量池存不存在"张三"这个字符串,如果存在,直接把"张三"的地址赋给s1,如果不存在,先在字符串常量池新建一个"张三",然后把地址返回给s1;
s2一样的道理,但是由于"张三"已经在字符串常量池,所以直接把"张三"的地址返回给s2;
s3的时候由于是new了一个String对象,所以s3是一个全新的对象,对象地址自然是堆中新对象的地址,但是这个对象中的"张三"还是指向的字符串常量池中的"张三";

下面我们加个方法

2.intern()

    String s1 = "张三";
    String s2= new String("张三");
    String s3 = s2.intern();
    System.out.println(s1 == s2); //false
    System.out.println(s1 == s3); //true
    System.out.println(s2 == s3); //false

也没什么难度,此时关系如下
image.png

String s3 = s2.intern()方法的意思是
如果字符串常量池存在"张三",那么就直接返回"张三"的地址
如果不存在,先在字符串常量池新建一个"张三",然后返回给变量,即s3,没什么争议。

既然知道是什么情况了,我们先来热下身

3.热身运动

  • 第一节
    String s1 = "张三";
    String s2 = new String("张三");
    String s3 = new String("张三");

    System.out.println(s1 == s2);
    System.out.println(s1 == s3);
    System.out.println(s2 == s3);

根据上边的知识,应该没问题

false
false
false
  • 第二节
    String s1 = "张三";
    String s2 = new String("张三").intern();
    String s3 = new String("张三").intern();

    System.out.println(s1 == s2);
    System.out.println(s1 == s3);
    System.out.println(s2 == s3);

没问题

true
true
true

下面我们加大点难度

4. 不可能,一定是你错了

4.1 我不可能错
    String name1 = "张";
    String name2 = "三";

    String s1 = name1 + name2;
    String s2 = new String("张三").intern();
    String s3 = new String("张三").intern();

    System.out.println(s1 == s2);
    System.out.println(s1 == s3);
    System.out.println(s2 == s3);

猜猜这次的结果是什么

false
false
true

由于字符串相加的效率并不高,所以java编译器编译的时候会把+操作转换成StringBuilder的append()操作,最后返回结果集的时候时候调用的方法的原理就是new String();感兴趣的小伙伴可以用javap命令反编译class文件看一下
image.png

如果说这次是因为不了解底层原理,好,那我们接下来看下一个

4.2 这不怪我

我们先看一个正常的

    String s1 = "张三";
    String s2 = new String("张") + new String("三");
    String s3 = s2.intern();

    System.out.println(s1 == s2); // false
    System.out.println(s2 == s3); // false
    System.out.println(s1 == s3); // true

这个没问题,下边我们把s1换个位置

    String s2 = new String("张") + new String("三");
    String s3 = s2.intern();
    String s1 = "张三";

    System.out.println(s1 == s2);
    System.out.println(s1 == s3);
    System.out.println(s2 == s3);

我们这次只把s1的位置放到了s3的下边,能猜到结果吗

true
true
true

没错,三个结果都是true,从这里可以体现出字符串常量池在堆中,下面我们看下此时的关系图
image.png

s2运行的时候,"张"和"三"被单独放到了字符串常量池中,而"张三"只是被作为一个对象放到了堆中,并没有放到字符串常量池(编译期可以确认的才放到字符串常量池),所以s2在执行intern方法时,发现常量池没有,就把s2引用的"张三"这个对象的地址放到了字符串常量池(可通过地址直接访问s2的"张三"),最终张三只有一份,所以s2 == s3。
当执行String s1 = "张三" 时,根据运行时常量池地址发现存在"张三这条记录",所以就直接返回给了s1,所以三个是同一个对象。

看完以后是不是觉得自己又收获了很多,我们最后再加点代码巩固一下

5.最后的倔强

        String s1 = new String("张") + new String("三");
        String s2 = new String("张") + new String("三");
        String s3 = new String("张") + new String("三"));
        String s4 = s1.intern();
        String s5 = s2.intern();
        String s6 = s3.intern()
        String s7 = "张三";

        System.out.println(s1 == s2); // false
        System.out.println(s1 == s3); // false
        System.out.println(s1 == s4); // true
        System.out.println(s2 == s5); // false
        System.out.println(s3 == s6); // false
        System.out.println(s1 == s7); // true
        System.out.println(s2 == s7); // false
        System.out.println(s3 == s7); // false

根据上边的经验,这个应该不费什么劲就看出来了

什么时候字符串会被放到字符串常量池?

  1. 调用intern()方法,并且字符串常量池没有的情况下。
  2. 编译期就可以确认的字符串,并且字符串常量池没有的情况下。

最后

看到这,相信字符串之间的关系已经了解的差不多了,以后使用过程中也不会混乱了。

最后说下,最后两个实例,不同的java版本,运行的结果是不一样的,主要是因为在java7之前字符串常量池所在位置不一样,我使用的是Java8版本,结果以8版本为准。

你可能感兴趣的:(java,字符串)