字符串是不可变的。这到底是什么意思?有关字符串和内存使用情况的重要事实

2022-08-31 05:55:14

我在不可变字符串上编写了以下代码。

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


答案 1

在进一步讨论不可变性之前,让我们先看一下该类及其功能,然后再得出任何结论。String

这是工作原理:String

String str = "knowledge";

像往常一样,这将创建一个包含的字符串,并为其分配一个引用。够简单吗?让我们执行更多功能:"knowledge"str

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

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

  str = str.concat(" base");

这会将字符串追加到 。但是等等,这怎么可能,因为对象是不可变的?令你惊讶的是,是的。" base"strString

执行上述语句时,VM 采用 的值,即 和附加 ,给我们值 。现在,由于 s 是不可变的,VM 无法将此值分配给 ,因此它会创建一个新对象,为其提供值 ,并为其提供引用 。String str"knowledge"" base""knowledge base"StringstrString"knowledge base"str

这里需要注意的重要一点是,虽然对象是不可变的,但其参考变量不是。因此,这就是为什么在上面的示例中,引用引用新形成的对象。StringString

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

有关字符串和内存使用情况的重要事实

如果我们没有另一个引用呢?我们会失去它。但是,它仍然存在,但由于没有引用,将被视为丢失。再看一个例子s"knowledge"String

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

发生了什么事情:

  1. 第一行非常简单:创建一个新的并引用它。String"java"s1
  2. 接下来,VM 将创建另一个新 ,但没有任何内容引用它。因此,第二个立即丢失。我们无法触及它。String"java rules"String

引用变量仍引用原始 。s1String"java"

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

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

当编译器看到文本时,它会在池中查找 。如果找到匹配项,则对新文本的引用将定向到现有文本,并且不会创建新对象。现有的只是有一个额外的参考。以下是使对象不可变的要点:StringStringStringStringStringString

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

好吧,现在你可以说,如果有人覆盖了String类的功能怎么办?这就是 String 类被标记为 final 的原因,这样就没有人可以重写其方法的行为。


答案 2

字符串是不可变的意味着您无法更改对象本身,但可以更改对对象的引用。

执行 时,您实际上是在更改 对由 String 文本创建的新对象的引用。a = "ty"a"ty"

更改对象意味着使用其方法更改其字段之一(或者这些字段是公共的而不是最终的,以便可以从外部更新它们而无需通过方法访问它们),例如:

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 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"