不可变到底是什么意思——也就是说,对象可变或不可变的结果是什么?特别是,为什么Java的字符串是不可变的?

我的理解是StringBuilder类型类似于String的可变等价。什么时候我会使用StringBuilder而不是字符串,反之亦然?


当前回答

不可变对象

一个对象被认为是不可变的,如果它的状态在被构造后不能改变。对于创建简单、可靠的代码,最大程度地依赖于不可变对象是一种被广泛接受的合理策略。

不可变对象在并发应用程序中特别有用。由于它们不能改变状态,因此不会被线程干扰破坏或在不一致的状态下观察到。

程序员通常不愿意使用不可变对象,因为他们担心创建新对象的成本,而不是在适当的地方更新对象。对象创建的影响经常被高估,并且可以被一些与不可变对象相关的效率所抵消。这包括减少垃圾收集带来的开销,以及消除保护可变对象不受损坏所需的代码。

下面的子节采用一个实例是可变的类,并从中派生一个具有不可变实例的类。在这样做的过程中,他们给出了这种转换的一般规则,并演示了不可变对象的一些优点。

其他回答

String s1="Hi";
String s2=s1;
s1="Bye";

System.out.println(s2); //Hi  (if String was mutable output would be: Bye)
System.out.println(s1); //Bye

s1="Hi":创建一个包含"Hi"值的对象s1。

S2 =s1:引用s1对象创建对象S2。

s1="Bye":之前的s1对象的值不会改变,因为s1有String类型,而String类型是一个不可变的类型,相反,编译器创建了一个新的String对象与"Bye"值和s1引用它。在这里,当我们打印s2值时,结果将是“Hi”而不是“Bye”,因为s2引用了前一个具有“Hi”值的s1对象。

“不可变”意味着你不能改变值。如果你有一个String类的实例,你调用的任何方法,只要看起来是在修改这个值,实际上都会创建另一个String。

String foo = "Hello";
foo.substring(3);
<-- foo here still has the same value "Hello"

要保存更改,您应该这样做 Foo = Foo .sustring(3);

当您使用集合时,不可变与可变可能会很有趣。想想如果使用可变对象作为map的键,然后更改值会发生什么(提示:考虑equals和hashCode)。

不可变对象是不能通过编程改变的对象。它们特别适用于多线程环境或其他多个进程能够更改(突变)对象中的值的环境。

不过,澄清一下,StringBuilder实际上是一个可变对象,而不是不可变对象。常规的java String是不可变的(意味着一旦创建了它,就不能在不改变对象的情况下更改底层字符串)。

例如,假设我有一个名为ColoredString的类,它有一个String值和一个String颜色:

public class ColoredString {

    private String color;
    private String string;

    public ColoredString(String color, String string) {
        this.color  = color;
        this.string = string;
    }

    public String getColor()  { return this.color;  }
    public String getString() { return this.string; }

    public void setColor(String newColor) {
        this.color = newColor;
    }

}

在这个例子中,ColoredString被认为是可变的,因为您可以在不创建新的ColoredString类的情况下更改(突变)它的一个关键属性。这可能很糟糕的原因是,例如,假设您有一个GUI应用程序,它有多个线程,并且您正在使用ColoredStrings将数据打印到窗口。如果你有一个ColoredString的实例,它被创建为

new ColoredString("Blue", "This is a blue string!");

然后你会期望字符串总是“Blue”。然而,如果另一个线程获得了这个实例并调用

blueString.setColor("Red");

当你想要一个“蓝色”的字符串时,你会突然,很可能出乎意料地得到一个“红色”的字符串。正因为如此,在传递对象实例时,几乎总是首选不可变对象。在确实需要可变对象的情况下,通常只需从特定的控制字段传递副本来保护对象。

概括一下,在Java中,Java .lang. string是一个不可变对象(一旦创建就不能更改),而Java .lang. stringbuilder是一个可变对象,因为它可以在不创建新实例的情况下进行更改。

In large applications its common for string literals to occupy large bits of memory. So to efficiently handle the memory, the JVM allocates an area called "String constant pool".(Note that in memory even an unreferenced String carries around a char[], an int for its length, and another for its hashCode. For a number, by contrast, a maximum of eight immediate bytes is required) When complier comes across a String literal it checks the pool to see if there is an identical literal already present. And if one is found, the reference to the new literal is directed to the existing String, and no new 'String literal object' is created(the existing String simply gets an additional reference). Hence : String mutability saves memory... But when any of the variables change value, Actually - it's only their reference that's changed, not the value in memory(hence it will not affect the other variables referencing it) as seen below....


字符串s1 = "旧字符串";

//s1 variable, refers to string in memory
        reference                 |     MEMORY       |
        variables                 |                  |

           [s1]   --------------->|   "Old String"   |

字符串s2 = s1;

//s2 refers to same string as s1
                                  |                  |
           [s1]   --------------->|   "Old String"   |
           [s2]   ------------------------^

s1 = "New String";

//s1 deletes reference to old string and points to the newly created one
           [s1]   -----|--------->|   "New String"   |
                       |          |                  |
                       |~~~~~~~~~X|   "Old String"   |
           [s2]   ------------------------^

原来的字符串'in memory'没有改变,但是 引用变量已被更改,以便它引用新字符串。 如果我们没有s2, Old String仍然在内存中,但是 我们无法访问它…

其中一个含义与值如何存储在计算机中有关,例如,对于一个。net字符串,它意味着内存中的字符串不能被更改,当你认为你正在更改它时,你实际上是在内存中创建了一个新的字符串,并将现有的变量(这只是一个指向其他地方的实际字符集合的指针)指向新的字符串。