Java中的可变字符串

13

几乎所有人都知道Java中的字符串是不可变的。最近我发现了一些东西,可能表明这并不总是正确的。让我们来试试这段代码:

System.out.println("-------- BEFORE MODIFICATIONS --------");
String beforeTest = new String("Original");
System.out.println(beforeTest);
java.lang.reflect.Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
valueField.set("Original", "Modified".toCharArray());
System.out.println("-------- AFTER MODIFICATIONS --------");
System.out.println(beforeTest);
System.out.println("Original");
String test = new String("Original");
System.out.println(test);
String test2 = new String("Original 2");
System.out.println(test2);

输出将为:

-------- BEFORE MODIFICATIONS --------
Original
-------- AFTER MODIFICATIONS --------
Original
Modified
Modified
Original 2

这个技巧是如何工作的?JVM是如何知道哪些对象应该被更改,哪些不应该被更改的?这个技巧背后有什么机制?为什么已经创建的beforeTest字符串没有被更改?这个技巧是否真的违反了字符串是不可变的原则?


8
反思是黑魔法巫术。 - Hovercraft Full Of Eels
1
@HovercraftFullOfEels,反射是完全定义良好的。只有当您通过调用setAccessible违反了private时,核心类不变式才会失效。 - Mike Samuel
3
@MikeSamuel 反射本身是有明确定义的,但使用它则不是,因此一旦开始搞砸不可搞砸的事情,就会出现无法理解的情况。我已经为此制定了一个完整的框架(Muckito)。 - Dave Newton
这是一个链接 https://www.pushkarrajpujari.com/article/strings-in-java/ - Pushkarraj Pujari
1个回答

19

字符串字面量会进入池中。这意味着当您编写以下代码时:

String s1 = "Foo";
String s2 = "Foo";
String s3 = new String("Foo");

s1和s2引用了同一个String对象,而s3引用了另一个由另一个字符数组支持的对象。

在您的代码中,您通过修改“原始”字符串字面值实例的私有字符数组违反了String的不变性。但是,由于beforeTest引用另一个String实例,因此它未被修改。

通过将字段保持为对象的私有状态并不提供任何修改该私有状态的方法可以实现不可变性。通过使用反射,您打破了封装的所有规则,因此可以违反不可变性。


第三个实例(测试引用)不也指向新实例吗?如果将“Original”文字字符替换为“Modified”,那么beforeTest的值不应该更新(因为相同的文字字符也被传递给实例)吗? - kosa
test变量被初始化为“原始”字符串文字的一个副本。但是在创建副本时,您已经修改了其内容为“修改”。因此,它是“修改”的副本。beforeTest也是“原始”字符串文字的一个副本,但是创建副本时,您尚未修改其内容。 - JB Nizet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接