1. String的不可变性

一旦一个String对象在内存中创建, 它将不可改变, 所有String类中方法并不是改变String对象自己, 而是重新创建一个新的String对象 .

java String 在输出语句是值吗不是地址吗_java


第一行在常量池中创建一个”abc”对象,

第二行进行截取, 其实是重新创建一个对象, 然后让a重新指向这个截取对象 .

但是原来”abc”对象并没有变化 , 只是没有引用指向它, 最后被垃圾回收 .

也就是”abc”, 一旦创建就不会被修改 . 这就是不可变性 .

查看String源码 :

java String 在输出语句是值吗不是地址吗_string_02


它的成员变量都是final, 尤其是value成员, 被final修饰, 表示这个变量一旦通过构造函数生成就不能被改变 .

所以String的不可变性并不是因为类被声明final(类被声明final只能代表可不可被继承), 真正决定不可变性, 是成员变量都被声明为final .

2. 代码

*常量 : final String a = "abc", 被final修饰, 那么a就是常量.
public static void main(String[] args) {
        /**
         * 1.常量池
         * JVM存在一个常量池,其中保存很多String对象,并且可以被共享,提高效率
         * 由于String类中成员是final, 它的值一旦创建不能被修改
         * 字符串池由String类维护, 可以调用intern方法访问字符串池 . 
         */
        // 字符串池创建一个对象
        String s1 = "abc";
        // 字符串池中存在"abc",所以这次不需要创建对象
        String s2 = "abc";
        // 所以两个地址指向一致 . 
        System.out.println("s1 == s2:"+(s1 == s2)); // true

        /**
         * 2.new String("")
         */
        // 创建两个对象, 一个存在字符串池中, 一个存在堆中
        String s3 = new String("abc");
        // 池中已经存在"abc"对象, 所以只在堆中创建
        String s4 = new String("abc");
        // 所以s3和s4不相等,都在堆中,指向的内存区域不同
        System.out.println("s3 == s4:"+(s3 == s4)); // false
        // 一个在pool中另一个在堆中
        System.out.println("s1 == s3:"+(s1 == s3)); // false

        /**
         * 3.常量的值在编译时已经确定(优化)
         * 这里"ab"和"cd"都是常量, 因此"+"之后值在编译时确定
         * 等同于 str = "abcd"
         */
        String s5 = "ab" + "cd";
        String s6 = "abcd";
        System.out.println("s5 == s6:"+(s5 == s6)); // true
        /**
         * 4.局部变量s7,s8存储两个拘留字符串对象的地址 . 
         * 那么下面(s7+s8)原理 :
         * 运行期JVM首先会在堆中创建一个StringBuilder对象,
         * 然后利用s7指向的拘留字符串完成初始化
         * 然后调用append方法完成对s8指向的字符串进行合并
         * 然后调用toString方法在堆中创建一个String对象
         * 最后将刚生成的String对象地址存放在s9中.
         * s10存储的是字符串池中"abcd"对应的地址.
         * s9存储是堆中"abcd"对应地址
         */
        String s7 = "ab";
        String s8 = "cd";
        String s9 = s7 + s8;
        String s10 = "abcd";
        System.out.println("s9 == s10:"+(s9 == s10)); // false

        /**
         * 5.java编译器对String+基本类型/常量, 当成常量表达式直接求值优化
         * 运行期两个string相加,会产生新的对象, 存在堆中
         */
        String s11 = "b";
        String s12 = "a" + s11;
        String s13 = "ab";
        //因为s11是变量(被final修饰是常量),所以运行期才会被解析
        System.out.println("s12 == s13:"+(s12 == s13) ); // false
        final String s14 = "b";
        String s15 = "a" + s14;
        String s16 = "ab";
        System.out.println("s15 == s16:"+(s15 == s16)); // true
    }

面试题:
曾经一个面试题 :

public static void main(String[] args) {
        String a = "hello";
        String b = "hel" + "lo";
        String c = "hel" + new String("lo");
        final String d = "lo";
        String e = "lo";
        String f = "hel" + d;
        String g = "hel" + e;
        System.out.println(a == b); // true b是常量相加
        System.out.println(a == c); // false new String会重新新建对象
        System.out.println(a == f); // true d是常量(被final修饰)
        System.out.println(a == g); // false e是变量不会编译期优化
    }

3.以前疑惑

对于String a = “abc”, 我总在思考 a 到底是字符串常量还是变量 . 你说它是常量, 他却没有被final修饰, 如果说变量但是”abc”会被放在常量池中 .
最后别人一句话点醒了我, a 是变量, “abc”是常量 . 我以前老纠结在 a 上, 导致概念各种混乱 .
如果a要变成常量 , 需要被final修饰 , final String a = “abc” .