首页 文章

字符串是不可变的 . 究竟是什么意思? [重复]

提问于
浏览
172

这个问题在这里已有答案:

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

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 is immutable ?你能帮我澄清这个话题吗?

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

19 回答

  • 3

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

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

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

    这是不可变的 .

  • 2

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

  • 5

    在继续进行不变性的大惊小怪之前,让我们先看一下 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 对象是不可变的, its reference variable is not. 这就是为什么在上面的例子中,引用是为了引用一个新形成的 String 对象 .

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

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

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

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

    What's happening:

    • 第一行非常简单:创建一个新的 String "java" 并将 s1 引用到它 .

    • 接下来,VM创建另一个新的 String "java rules" ,但没有任何内容引用它 . 所以,第二个 String 立即丢失 . 我们无法达到它 .

    引用变量 s1 仍然引用原始 String "java" .

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

    随着应用程序的增长, String 文字占用大面积内存非常常见,甚至可能导致冗余 . 所以,为了提高Java的效率, the JVM sets aside a special area of memory called the "String constant pool".

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

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

    那么,现在你可以说,如果有人重写 String 类的功能会怎么样?这就是为什么没有人可以覆盖其方法的行为的原因 the String class is marked final .

  • 4

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

    当您执行 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,即:

    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

    你正在改变 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";
    
  • 340

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

    例如 .

    String s
    

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

    你也应该意识到这一点

    new String(s)
    

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

    "my string" 这样的Java双引号字符串实际上是对interned String 实例的引用所以 "bar" 是对同一个String实例的引用,无论它在代码中显示多少次 .


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

  • 2

    看这里

    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 的内容,都将创建一个新对象 . 即不允许更改不可变对象的内容 . 这就是为什么对象的地址都不同的原因 .

  • 1

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

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

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

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

  • 2

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

    看到

    Immutable objects on Wikipedia

  • 1

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

    所以 a = "ABC" < - 不可变对象 . "a"保存对象的引用 . 并且, a = "DEF" < - 另一个不可变对象,"a"现在引用它 .

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

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

  • 1
    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 . 小号

  • 170

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

    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);
    
  • 1

    Java String 是不可变的, String 将以对象的形式存储值 . 因此,如果您指定值 String a="a"; ,它将创建一个对象,并且该值存储在该对象中并且如果您要分配值 a="ty" 意味着它将创建另一个对象存储该值,如果您想要清楚地理解,请检查 has codeString .

  • 2

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

  • 2

    在您的示例中, 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",因为我们永远不会改变 b 指向的 String 实例 .

  • 3

    如果某个对象 bar 拥有对可变对象 foo 的引用并将其某些状态封装在 foo 状态的可变方面,那么将允许可以改变 foo 的那些方面的代码来改变 bar 状态的相应方面而不实际触摸 bar 甚至知道它的存在 . 通常,这意味着使用可变对象封装其自身状态的对象必须确保不会将对这些对象的引用暴露给可能会意外地改变它们的任何代码 . 相比之下,如果 bar 持有对象 moo 的引用并且仅使用 moo 以外的不可变方面来封装其状态,那么 bar 可以自由地将 moo 暴露给外部代码而不用担心外部代码可能对它做什么 .

  • 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

  • 18

    可能上面提供的每个答案都是正确的,但我的答案是特定于使用 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());
    
        }
    }
    

    OUTPUT

    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
    
  • 1

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

相关问题