我在不可变字符串上写了下面的代码。

public class ImmutableStrings {

    public static void main(String[] args) {
        testmethod();
    }

    private static void testmethod() {
        String a = "a";
        System.out.println("a 1-->" + a);
        a = "ty";
        System.out.println("a 2-->" + a);
    }
}

输出:

a 1-->a  
a 2-->ty

这里变量a的值被改变了(尽管许多人说不可变对象的内容不能被改变)。但是说字符串是不可变的到底是什么意思呢?你能给我解释一下这个话题吗?

来源:https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html


当前回答

希望下面的代码能澄清你的疑问:

public static void testString() {
    String str = "Hello";
    System.out.println("Before String Concat: "+str);
    str.concat("World");
    System.out.println("After String Concat: "+str);
    StringBuffer sb = new StringBuffer("Hello");
    System.out.println("Before StringBuffer Append: "+sb);
    sb.append("World");
    System.out.println("After StringBuffer Append: "+sb);
}

字符串Concat:你好 字符串后Concat:你好 在StringBuffer追加之前:Hello StringBuffer后追加:HelloWorld

其他回答

你不是在改变赋值语句中的对象,而是用另一个不可变对象替换了一个不可变对象。Object String("a")不会变成String("ty"),它会被丢弃,对ty的引用会被写入a中。

相反,StringBuffer表示一个可变对象。你可以这样做:

StringBuffer b = new StringBuffer("Hello");
System.out.writeln(b);
b.append(", world!");
System.out.writeln(b);

在这里,您没有重新分配b:它仍然指向相同的对象,但该对象的内容已经更改。

If some object bar holds a reference to a mutable object foo and encapsulates some of its state in mutable aspects of foo's state, that will allow code which can change those aspects of foo to change the corresponding aspects of bar's state without actually touching bar or even knowing of its existence. Generally, this means that objects which encapsulate their own state using mutable objects must ensure that no references to those objects are exposed to any code which might unexpectedly mutate them. By contrast, if bar holds a reference to an object moo and only uses immutable aspects of moo other than identity to encapsulate its state, then bar can freely expose moo to outside code without worrying about anything the outside code might do to it.

希望下面的代码能澄清你的疑问:

public static void testString() {
    String str = "Hello";
    System.out.println("Before String Concat: "+str);
    str.concat("World");
    System.out.println("After String Concat: "+str);
    StringBuffer sb = new StringBuffer("Hello");
    System.out.println("Before StringBuffer Append: "+sb);
    sb.append("World");
    System.out.println("After StringBuffer Append: "+sb);
}

字符串Concat:你好 字符串后Concat:你好 在StringBuffer追加之前:Hello StringBuffer后追加:HelloWorld

在进一步讨论不可变性之前,让我们在得出任何结论之前稍微了解一下String类及其功能。

这是String的工作原理:

String str = "knowledge";

这,像往常一样,创建一个包含“知识”的字符串,并为其分配一个引用str。简单吗?让我们执行更多的函数:

 String s = str;     // assigns a new reference to the same string "knowledge"

让我们看看下面的语句是如何工作的:

  str = str.concat(" base");

这将追加一个字符串“base”到str。但等一下,这是怎么可能的,因为字符串对象是不可变的?出乎你的意料,它是。

当执行上述语句时,虚拟机接受String str的值,即。“knowledge”并附加“base”,赋予我们“knowledge base”这个值。现在,由于字符串是不可变的,VM不能将这个值分配给str,所以它创建了一个新的String对象,给它一个值“知识库”,并给它一个引用str。

这里需要注意的一点是,虽然String对象是不可变的,但它的引用变量不是。这就是为什么在上面的例子中,引用被用来引用一个新形成的String对象。

在上面例子的这一点上,我们有两个String对象:第一个是我们用值"knowledge"创建的,由s指向,第二个是"knowledge base",由str指向。但是,从技术上讲,我们有三个String对象,第三个是concat语句中的文字"base"。

关于字符串和内存使用的重要事实

如果我们对“知识”没有另一种指称会怎样?我们会失去那个字符串。然而,它仍然存在,但由于没有参考资料,将被视为丢失。 下面再看一个例子

String s1 = "java";
s1.concat(" rules");
System.out.println("s1 refers to "+s1);  // Yes, s1 still refers to "java"

发生了什么:

第一行非常简单:创建一个新的String "java",并将s1引用给它。 接下来,虚拟机创建另一个新的字符串“java rules”,但是什么都没有 指它。因此,第二个字符串立即丢失。我们够不到 它。

引用变量s1仍然引用原始字符串“java”。

几乎每个应用于String对象以修改它的方法都会创建新的String对象。那么,这些String对象在哪里呢?这些都存在于内存中,任何编程语言的关键目标之一就是有效地利用内存。

随着应用程序的增长,字符串字面值占用大量内存是很常见的,这甚至会导致冗余。因此,为了使Java更高效,JVM留出了一个特殊的内存区域,称为“字符串常量池”。

当编译器看到String字面值时,它会在池中查找String。如果找到匹配,则对新文字的引用将指向现有的String,并且不会创建新的String对象。现有的String只是多了一个引用。下面是使String对象不可变的要点:

在String常量池中,一个String对象可能有一个或多个引用。如果几个引用指向同一个String而不知道它,如果其中一个引用修改了该String值,那就不好了。这就是为什么String对象是不可变的。

现在你可以说,如果有人重写了String类的功能呢?这就是String类被标记为final的原因,这样任何人都不能重写其方法的行为。

可能上面提供的每个答案都是正确的,但我的答案是特定于使用hashCode()方法,以证明诸如,字符串…一旦创建就不能修改,修改后会在不同的内存位置产生新的值。

public class ImmutabilityTest {

    private String changingRef = "TEST_STRING";

    public static void main(String a[]) {

        ImmutabilityTest dn = new ImmutabilityTest();

        System.out.println("ChangingRef for TEST_STRING OLD : "
                + dn.changingRef.hashCode());

        dn.changingRef = "NEW_TEST_STRING";
        System.out.println("ChangingRef for NEW_TEST_STRING : "
                + dn.changingRef.hashCode());

        dn.changingRef = "TEST_STRING";
        System.out.println("ChangingRef for TEST_STRING BACK : "
                + dn.changingRef.hashCode());

        dn.changingRef = "NEW_TEST_STRING";
        System.out.println("ChangingRef for NEW_TEST_STRING BACK : "
                + dn.changingRef.hashCode());

        String str = new String("STRING1");
        System.out.println("String Class STRING1 : " + str.hashCode());

        str = new String("STRING2");
        System.out.println("String Class STRING2 : " + str.hashCode());

        str = new String("STRING1");
        System.out.println("String Class STRING1 BACK : " + str.hashCode());

        str = new String("STRING2");
        System.out.println("String Class STRING2 BACK : " + str.hashCode());

    }
}

输出

ChangingRef for TEST_STRING OLD : 247540830
ChangingRef for NEW_TEST_STRING : 970356767
ChangingRef for TEST_STRING BACK : 247540830
ChangingRef for NEW_TEST_STRING BACK : 970356767
String Class STRING1 : -1163776448
String Class STRING2 : -1163776447
String Class STRING1 BACK : -1163776448
String Class STRING2 BACK : -1163776447