半步多 玄玉的博客

String对象与StringPool之间的是是非非

2010-10-24
玄玉

package com.jadyer.demo;

public class StringPoolTest {
    public static void main(String[] args) {
        /*
         * 执行完该行代码,会在内存中生成两个对象(一个在StringPool中,一个在堆内存中),它们的内容都是abc
         * ---------------------------------------------------------------------------------------------------------
         * 1、这里的s不是对象,s是对象的地址,叫做引用(或句柄),它指向的是堆内存中的对象
         * 2、由于经常要用到String类的对象,所以JVM内部为了能够重复使用,便专门用一片内存空间缓冲已存在的String对象
         *    即字符串常量池StringPool,它是java.lang.String类所特有的,它的作用域是整个虚拟机中(可笼统理解为一个工程中)
         * ---------------------------------------------------------------------------------------------------------
         * 在生成字符串对象的时候,String的执行流程如下:
         * 首先到StringPool中查找是否存在内容为abc的对象,若已存在,那么它就不会在StringPool中创建abc对象了
         * 但由于该行代码是main()方法的第一行语句,所以此时StringPool中是空的,是没有对象的
         * 于是它就会把abc对象放到StringPool中,接下来它去执行[new String("abc")]构造方法
         * 我们知道:new关键字表示生成一个对象,这个对象是在Java的堆内存中的
         * 所以接下来它就在Java的堆内存中又生成一个内容为abc的对象
         * 这样就造成了StringPool中有一个abc对象,堆内存中也有一个abc对象
         * ---------------------------------------------------------------------------------------------------------
         */
        String s = new String("abc");

        /*
         * 执行完该行代码,内存中不会生成新的对象
         * ---------------------------------------------------------------------------------------------------------
         * 由于这里是通过直接给出字面值"abc",而不是new的方式为字符串赋值
         * 所以这种情况下,Java首先会到StringPool中查找有没有内容为abc的字符串对象存在
         * 若没找到,那么便会在StringPool中新创建一个abc对象,再将引用指向新创建的abc对象
         * 若找到了,那么它就不会在StringPool中生成新的字符串对象了,转而使用已存在的,并将s1引用指向StringPool中的abc对象
         * ---------------------------------------------------------------------------------------------------------
         */
        String s1 = "abc";

        /*
         * 执行完该行代码,内存中一共有三个对象
         * ---------------------------------------------------------------------------------------------------------
         * 执行过程中,它会首先查看一下StringPool中有没有abc对象存在
         * 结果发现:有
         * 那么接着它就不会在StringPool中创建新的abc对象
         * 由于它是通过[new String("abc")]方式为字符串赋值的,
         * 我们知道:只要Java中有new操作的话,就表示它会生成一个新的对象(不管多少次,都会生成新的,且新对象都是在堆内存中)
         * 所以执行该行代码时,它在堆内存中又会生成一个新的abc对象,并将其引用地址赋给s2
         * ---------------------------------------------------------------------------------------------------------
         */
        String s2 = new String("abc");

        /**
         * 也就是说,在执行完前面的三行语句后,内存中共有三个对象
         * 其中包含了StringPool中的一个对象和堆内存中的两个对象
         */

        /*
         * 字符串的相等判断
         * ---------------------------------------------------------------------------------------------------------
         * 对于Java中的8个原生数据类型,==比较的是它们的字面值是否相同
         * 对于引用类型,它所比较的:永远永远都是两个对象的内存地址(即两个引用是不是指向同样的一个对象)
         * ---------------------------------------------------------------------------------------------------------
         * 这里s、s1、s2分别指向三个不同的对象,这三个对象分别在内存中不同的地方
         * 所以s、s1、s2中任意两个使用==比较时,由于三者的内存地址均不同,故返回值都是false
         * ---------------------------------------------------------------------------------------------------------
         */
        System.out.println(s == s1);
        System.out.println(s == s2);
        System.out.println(s1 == s2);

        System.out.println("~~~~~~飘逸的分隔线01~~~~~~");

        System.out.println(s.equals(s1));
        System.out.println(s.equals(s2));
        System.out.println(s1.equals(s2));

        System.out.println("~~~~~~飘逸的分隔线02~~~~~~");

        /*
         * java.lang.String.intern()
         * 如果StringPool中已经包含一个等于此String对象的字符串,则返回StringPool中的字符串
         * 否则,将此String对象添加到StringPool中,并返回StringPool中的此String对象的引用
         * ---------------------------------------------------------------------------------------------------------
         * 调用s.intern()时
         * 它首先会先查StringPool中是否存在内容为abc的对象,结果发现:有
         * 这时它就会将s.intern()的返回值指向StringPool中的abc对象
         * 换句话说:s.intern()返回的是StringPool中的abc对象的地址,即与s1相等的值
         * 所以[s == s.intern()]的判断就相当于拿s和s1进行==判断,结果当然会返回false
         * ---------------------------------------------------------------------------------------------------------
         * 调用s1.intern()时
         * 它还是会检查StringPool中是否存在内容为abc的对象,结果发现:有
         * 这时它同样会将StringPool中abc对象的地址返回赋给s1.intern()的返回值
         * 而s1本身指向的就是StringPool中的abc对象,所以[s1 == s1.intern()]判断的结果即true
         * ---------------------------------------------------------------------------------------------------------
         * 基于同样道理:s.intern()和s2.intern()的返回值都是StringPool中的abc对象的地址,所以为true
         * ---------------------------------------------------------------------------------------------------------
         */
        System.out.println(s == s.intern());
        System.out.println(s1 == s1.intern());
        System.out.println(s.intern() == s2.intern());

        System.out.println("~~~~~~飘逸的分隔线03~~~~~~");

        /*
         * 字符串的加号操作
         * ---------------------------------------------------------------------------------------------------------
         * 如果加号左右两边的操作数都是字面值(即常量值)
         * 那么它会将这两个字面值拼起来,并得到一个对象,然后检查StringPool中有没有该对象存在
         * 如果StringPool中没有该对象的话,那么就把它放进去
         * 如果StringPool中存在该对象的话,则不生成新的对象,而是直接返回StringPool中的该对象
         * 所以:["hel" + "lo"]最终会拼成"hello",并且它返回的是StringPool中hello的地址
         * 所以:[hello == "hel" + "lo"]判断的结果即为true
         * ---------------------------------------------------------------------------------------------------------
         * 如果加号左右两边有一个操作数不是常量的话,即有一个是变量的话
         * 那么在将这两个操作数的值拼起来之后,就不会检查StringPool而是直接在堆内存中生成新对象
         * 所以:["hel" + lo]最终也会拼成"hello",但不同的是它所拼成的"hello"不是StringPool中的
         * 所以:而是在Java堆内存中新生成的一个"hello"对象
         * 那么:既然一个指向堆内存,一个指向StringPool,则二者肯定不是同一个对象,所以[hello == "hel" + lo]的结果为false
         * 同理:[hello == hel + lo]判断的结果亦为false
         * ---------------------------------------------------------------------------------------------------------
         * final:如果将lo变成final的话,那么[hello == "hel" + lo]的结果就是true
         * ---------------------------------------------------------------------------------------------------------
         */
        String hello = "hello";
        String hel = "hel";
        String lo = "lo";
        //final String lo = "lo";
        System.out.println(hello == "hel" + "lo");
        System.out.println(hello == "hel" + lo);
        System.out.println(hello == hel + lo);
    }
}

控制台输出如下

false
false
false
~~~~~~飘逸的分隔线01~~~~~~
true
true
true
~~~~~~飘逸的分隔线02~~~~~~
false
true
true
~~~~~~飘逸的分隔线03~~~~~~
true
false
false

Content