字符串是不可变的。究竟是什么意思?

时间:2012-01-10 04:04:14

标签: java string immutability

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

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的值已经改变(虽然很多人说不可变对象的内容不能改变)。但是, String是不可改变的,究竟是什么意思呢?你能否为我澄清一下这个话题?

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

19 个答案:

答案 0 :(得分:372)

在继续进行 immutability 的大惊小怪之前,让我们先看一下String类及其功能,然后再得出任何结论。

这就是String的工作原理:

String str = "knowledge";

这通常会创建一个包含"knowledge"的字符串,并为其指定引用str。够简单吗?让我们执行更多功能:

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

让我们看看下面的陈述是如何运作的:

  str = str.concat(" base");

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

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

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

在上面的示例中,我们有两个String个对象:我们创建的第一个对象"knowledge",由s指向,第二个{{1} } {,"knowledge base"指出。但是,从技术上讲,我们有三个str个对象,第三个是String语句中的文字"base"

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

如果我们没有其他参考concats怎么办?我们会丢失"knowledge"。但是,它仍然存在,但由于没有引用而被视为丢失。 再看下面的另一个例子

String

发生了什么:

  1. 第一行非常简单:创建一个新的String s1 = "java"; s1.concat(" rules"); System.out.println("s1 refers to "+s1); // Yes, s1 still refers to "java" String并将"java"引用给它。
  2. 接下来,VM会创建另一个新的s1 String,但没有 是指它。所以,第二个"java rules"会立即丢失。我们无法达成 它。
  3. 引用变量String仍然引用原始s1 String

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

    随着应用程序的增长,{{1>}文字占据大面积内存非常常见,这甚至会导致冗余。因此,为了提高Java的效率, JVM预留了一个特殊的内存区域,称为"字符串常量池"。

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

    String常量池中,String对象可能有一个或多个引用。 如果多个引用指向同一个String而不知道它,那么如果其中一个引用修改了String值,则会很糟糕。这就是String个对象不可变的原因。

    嗯,现在你可以说,如果有人改写String类的功能会怎么样?这就是 String类的原因标记为String ,以便任何人都无法覆盖其方法的行为。

答案 1 :(得分:170)

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

答案 2 :(得分:18)

您正在更改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";

答案 3 :(得分:5)

字符串是char[],其中包含一系列UTF-16 code units,该数组的int偏移量,int长度。

例如。

String s

它为字符串引用创建空间。分配副本引用,但不修改这些引用引用的对象。

你也应该知道

new String(s)

并没有真正做任何有用的事情。它只创建另一个由同一数组,偏移量和长度支持的实例s。很少有理由这样做,因此被大多数Java程序员认为是不好的做法。

"my string"这样的Java双引号字符串实际上是对interned String个实例的引用,因此"bar"是对同一个String实例的引用,无论它出现多少次你的代码。


"你好"创建一个池化的实例,new String(...)创建一个非池化的实例。试试System.out.println(("hello" == "hello") + "," + (new String("hello") == "hello") + "," + (new String("hello") == new String("hello")));,您应该看到true,false,false

答案 4 :(得分:4)

immutable意味着您无法更改相同referance的值。每次创建新referance所需的时间都意味着新的内存位置。 例如:

String str="abc";
str="bcd";

这里,在上面的代码中,在内存中有2个用于存储值的块。第一个用于值“abc”,第二个用于“bcd”。第二个值不替换为第一个值。

这是调用不可变的。

答案 5 :(得分:3)

在您的示例中,变量a只是对字符串对象实例的引用。当你说a = "ty"时,你实际上并没有改变字符串对象,而是将引用指向一个完全不同的字符串类实例。

答案 6 :(得分:3)

见这里

class ImmutableStrings {

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

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

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

       System.out.println("a 2 address-->"+a.hashCode());
    }
}

输出:

a 1-->a
a 1 address-->97
a 2-->ty
a 2 address-->3717

这表明无论何时修改不可变字符串对象a的内容,都将创建一个新对象。即不允许更改不可变对象的内容。这就是为什么对象的地址都不同的原因。

答案 7 :(得分:2)

不可变对象是一个对象,其状态在创建后无法修改。

所以a = "ABC"< - 不可变对象。 “a”表示对象的引用。 并且,a = "DEF"< - 另一个不可变对象,“a”现在引用它。

分配字符串对象后,无法在内存中更改该对象。

总之,您所做的是将“a”的引用更改为新的字符串对象。

答案 8 :(得分:2)

您实际上是在获取对新字符串的引用,字符串本身未被更改,因为它是不可变的。这是相关的。

Immutable objects on Wikipedia

答案 9 :(得分:2)

您没有更改赋值语句中的对象,替换为另一个不可变对象。对象String("a")不会更改为String("ty"),它会被丢弃,而引用会被ty写入a

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

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

此处,您没有重新分配b:它仍然指向同一个对象,但该对象的内容已更改。

答案 10 :(得分:2)

String S1="abc";
S1.concat("xyz");
System.out.println("S1 is", + S1);
String S2=S1.concat("def");
System.out.println("S2 is", + S2);

这表明一旦创建了一个字符串对象,就无法更改它。你需要创建新的EveryTime并放入另一个String。小号

答案 11 :(得分:2)

我认为以下代码清除了差异:

String A = new String("Venugopal");
String B = A;

A = A +"mitul";

System.out.println("A is " + A);
System.out.println("B is " + B);

StringBuffer SA = new StringBuffer("Venugopal");
StringBuffer SB = SA;

SA = SA.append("mitul");

System.out.println("SA is " + SA);
System.out.println("SB is " + SB);

答案 12 :(得分:2)

Java String是不可变的,String将以对象的形式存储值。因此,如果你指定值String a="a";,它将创建一个对象,并且值将存储在该对象中,如果您正在分配值a="ty",则意味着它将创建另一个对象存储该值,如果您想清楚了解,请查看has code的{​​{1}}。

答案 13 :(得分:1)

在您的示例中,a首先引用"a",然后引用"ty"。你没有改变任何String个实例;您只是更改了String实例a引用的内容。例如,这个:

String a = "a";
String b = a; // b refers to the same String as a
a = "b"; // a now refers to a different instance
System.out.println(b);

打印“a”,因为我们从不改变String指向的b实例。

答案 14 :(得分:1)

只有参考正在改变。第一个a引用字符串“a”,稍后您将其更改为“ty”。字符串“a”保持不变。

答案 15 :(得分:1)

如果某个对象bar拥有对可变对象foo 的引用,并将其某些状态封装在foo状态的可变方面,那么将允许更改foo的这些方面的代码更改bar的州的相应方面,而不实际触及bar或甚至不知道其存在。通常,这意味着使用可变对象封装其自身状态的对象必须确保不会将对这些对象的引用暴露给可能意外地改变它们的任何代码。相反,如果bar包含对象moo的引用,并且仅使用moo 以外的bar 的不可变方面来封装其状态,那么{{1}可以自由地将moo公开给外部代码,而不必担心外部代码可能对它做任何事情。

答案 16 :(得分:1)

希望以下代码能澄清您的疑虑:

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);
}

String Concat之前:你好 String Concat之后:你好 在StringBuffer追加之前:你好 StringBuffer追加后:HelloWorld

答案 17 :(得分:1)

String是不可变的,这意味着,String对象的内容一旦创建就无法更改。如果要修改内容,则可以使用StringBuffer / StringBuilder而不是String。 StringBuffer和StringBuilder是可变类。

答案 18 :(得分:1)

上面提供的每个答案可能都是正确的,但我的回答是针对hashCode()方法的使用,以证明像String这样的点......一旦创建就无法修改,修改将导致不同记忆位置的新值。

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