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

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

其他回答

你改变了a指的东西。试试这个:

String a="a";
System.out.println("a 1-->"+a);
String b=a;
a="ty";
System.out.println("a 2-->"+a);
System.out.println("b  -->"+b);

您将看到a和b所指向的对象没有改变。

如果你想阻止你的代码改变a引用的对象,试试:

final String a="a";

在进一步讨论不可变性之前,让我们在得出任何结论之前稍微了解一下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的原因,这样任何人都不能重写其方法的行为。

字符串是不可变的,这意味着你不能改变对象本身,但是你可以改变对对象的引用。

当你执行a = "ty"时,你实际上是在改变a对一个由String字面值"ty"创建的新对象的引用。

改变一个对象意味着使用它的方法来改变它的一个字段(或者字段是公共的而不是final的,这样它们就可以从外部更新而不需要通过方法访问它们),例如:

Foo x = new Foo("the field");
x.setField("a new field");
System.out.println(x.getField()); // prints "a new field"

而在一个不可变类(声明为final,以防止通过继承修改)(它的方法不能修改它的字段,而且字段总是私有的,建议是final),例如String,你不能改变当前的String,但你可以返回一个新的String,即:

String s = "some text";
s.substring(0,4);
System.out.println(s); // still printing "some text"
String a = s.substring(0,4);
System.out.println(a); // prints "some"

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.

Java字符串是不可变的,字符串将以对象的形式存储值。所以如果你赋值String a="a";它将创建一个对象,值存储在其中,如果你分配值a="ty"意味着它将创建另一个对象存储在其中的值,如果你想清楚地理解,检查字符串的has代码。