关于java:String是不可变的。

String is immutable. What exactly is the meaning?

本问题已经有最佳答案,请猛点这里访问。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
    }
}

输出:

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

这里,变量a的值已经更改(许多人说不可变对象的内容不能更改)。但你说String是不变的,究竟是什么意思呢?你能为我澄清一下这个话题吗?

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


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

这就是String的工作原理:

1
String str ="knowledge";

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

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

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

1
  str = str.concat(" base");

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

执行上述语句时,vm取String str的值,即"knowledge"的值,并加上" base",得出"knowledge base"的值。现在,由于Strings是不可变的,vm不能把这个值赋给str,所以它创建了一个新的String对象,给它一个值"knowledge base",并给它一个引用str

这里需要注意的一点是,虽然String对象是不可变的,但其引用变量不是。因此,在上面的例子中,引用是指一个新形成的String对象。

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

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

如果我们没有另一个参考s"knowledge"呢?我们早就失去了那辆车。但是,它仍然存在,但是由于没有引用而被认为丢失。再看下面的一个例子

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

发生了什么事:

  • 第一行非常简单:创建一个新的String"java",并引用s1
  • 接下来,虚拟机创建另一个新的String"java rules",但没有指的是它。因此,第二个String立即丢失。我们够不着它。
  • 参考变量s1仍指原String"java"

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

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

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

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

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


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

    执行a ="ty"时,实际上是将a的引用更改为字符串文字"ty"创建的新对象。

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

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

    在不可变类(声明为final,以防止通过继承进行修改)中(其方法不能修改其字段,而且字段始终是私有的,建议为final),例如string,不能更改当前字符串,但可以返回新字符串,即:

    1
    2
    3
    4
    5
    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"


    你在改变a所指的。试试这个:

    1
    2
    3
    4
    5
    6
    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);

    您将看到,ab所指的对象没有改变。

    如果要防止代码更改a引用的对象,请尝试:

    1
    final String a="a";


    字符串是包含一系列utf-16代码单元的char[]、该数组中的int偏移量和int长度。

    例如。

    它为字符串引用创建空间。分配会复制周围的引用,但不会修改这些引用所引用的对象。

    你也应该知道

    1
    new String(s)

    没有什么有用的。它只创建另一个实例,由与s相同的数组、偏移量和长度来支持。很少有理由这么做,所以大多数Java程序员都认为这是一个错误的做法。

    Java双引号字符串(如EDCOX1×28)实际上是对接口EDCOX1和17个实例的引用,所以EDCOX1×30 }是对同一个字符串实例的引用,而不管它在代码中出现了多少次。

    "hello"创建一个集合实例,而new String(...)创建一个非集合实例。试试System.out.println(("hello" =="hello") +"," + (new String("hello") =="hello") +"," + (new String("hello") == new String("hello")));,你应该会看到true,false,false


    不可变意味着不能更改同一引用的值。每次需要创建新引用时,都意味着新的内存位置。前任:

    1
    2
    String str="abc";
    str="bcd";

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

    这就是所谓的不变。


    看到这里

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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());
        }
    }

    输出:

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

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


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


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

    相反,StringBuffer代表一个可变的对象。您可以这样做:

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

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


    实际上,您正在获取对一个新字符串的引用,因为该字符串本身是不可变的,所以不会被更改。这是相关的。

    维基百科上的不变对象


    不可变对象是在创建后其状态不能修改的对象。

    所以a ="ABC"是不变的对象。"保留对对象的引用。而且,a ="DEF"<--另一个不变的对象,"A"现在持有对它的引用。

    一旦分配了一个字符串对象,该对象就不能在内存中更改。

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


    1
    2
    3
    4
    5
    String S1="abc";
    S1.concat("xyz");
    System.out.println("S1 is", + S1);
    String S2=S1.concat("def");
    System.out.println("S2 is", + S2);

    这表明一旦创建了一个字符串对象,就不能更改它。每次需要创建新的并放入另一个字符串时。S


    我认为以下代码可以消除差异:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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);

    JavaEDCX1 0Ω是不可变的,EDCOX1×0将以对象的形式存储值。因此,如果u指定值String a="a";,它将创建一个对象,并将该值存储在该对象中;如果您指定值a="ty",它将创建另一个对象,并将该值存储在该对象中,如果您想清楚地了解,请检查has code中的String


    也许上面提供的每个答案都是对的,但我的答案是具体使用hashCode()方法,来证明点,如,字符串…一旦创建就不能修改,修改会在不同的内存位置产生新的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    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());

        }
    }

    产量

    1
    2
    3
    4
    5
    6
    7
    8
    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

    在您的示例中,a首先指"a",然后指"ty"。你不会改变任何一个String实例;你只是改变String实例a所指的。例如,这:

    1
    2
    3
    4
    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",因为我们从未对b指向的String实例进行变异。


    只有引用正在更改。首先,a引用了字符串"a",然后您将其更改为"ty"。字符串"a"保持不变。


    希望下面的代码可以澄清您的疑问:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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追加之前:您好在stringbuffer append:helloworld之后


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


    如果某个对象bar引用了一个可变对象foo并将它的某些状态封装在foo状态的可变方面,这将允许代码在不实际接触bar或甚至不知道的情况下改变foo状态的相应方面。它的存在。一般来说,这意味着使用可变对象封装其自身状态的对象必须确保对这些对象的引用不会暴露于任何可能使它们发生意外变化的代码中。相反,如果bar引用了一个对象moo并且只使用moo的不可变方面(除了标识)来封装它的状态,那么bar可以自由地将moo暴露于外部代码,而不必担心外部代码可能对它做什么。