话不多说,先上源码
两个成员变量

 /** The value is used for character storage. */
    private final char value[];//方法大多围绕这个属性来操作

    /** Cache the hash code for the string */
    private int hash; // Default to 0

解释

众所周知底层是char[],再来对修饰词分析
1、final
被final修饰的变量为常量,不能被再次赋值。这里String类初始化时给value[]赋值了。

//空构造器
public String() {
        this.value = "".value;
    }

既然为常量,表示value[]会放入常量池。
所以解释得通,面试题:new String(“abc”)创建了几个对象?(答案:一个或者两个)

首先会有个String对象,而类中的value[]数组是也是一个对象,这是第二个对象。
第个二对象的引用放在常量池里,根据final修饰的不可变性,如果有就不会再创建了。
即常量池里有答案就是一,常量池里没有会创建一个String对象时,再创建一个数组对象放常量池里。

2、private
final还不足以解决String类的不可变性,因为final修饰的是一个对象引用(再次数组是一个对象)。
即引用的指向不变,但对象里的内容可变。比如,数组虽然不能扩容(数组长度是固定的,如果要改变只能新建一个),但里面的元素可以替换,value[1]='a';。

所以,private把value[]私有化封装起来,并且不提供类似getter/setter等间接操作成员属性的方法。
可以看到String源码中的方法操作也避免操作原来的数组,而创建一个新的字符串返回。

  public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */
            //省略。。。
                }
                return new String(buf, true);
            }
        }
        return this;
    }

总结

final和private共同决定了,String类的不变性。

若有不当恳请指正。