我们都知道String在Java中是不可变的,但是检查下面的代码:

String s1 = "Hello World";  
String s2 = "Hello World";  
String s3 = s1.substring(6);  
System.out.println(s1); // Hello World  
System.out.println(s2); // Hello World  
System.out.println(s3); // World  

Field field = String.class.getDeclaredField("value");  
field.setAccessible(true);  
char[] value = (char[])field.get(s1);  
value[6] = 'J';  
value[7] = 'a';  
value[8] = 'v';  
value[9] = 'a';  
value[10] = '!';  

System.out.println(s1); // Hello Java!  
System.out.println(s2); // Hello Java!  
System.out.println(s3); // World  

为什么这个程序是这样运行的?为什么s1和s2的值变了,而s3的值不变?


当前回答

字符串在JVM堆内存的永久区域中创建。是的,它确实是不可变的,在创建之后就不能更改。 因为在JVM中,有三种类型的堆内存: 1. 年轻的一代 2. 老的代 3.永久的一代。

当创建任何对象时,它将进入年轻代堆区域和为字符串池保留的永久代区域。

以下是更多细节,你可以从中获取更多信息: 垃圾收集在Java中的工作原理。

其他回答

在Java中,如果两个字符串基元变量被初始化为相同的字面值,它会将相同的引用赋给这两个变量:

String Test1="Hello World";
String Test2="Hello World";
System.out.println(test1==test2); // true

这就是比较返回true的原因。第三个字符串是使用substring()创建的,它创建了一个新的字符串,而不是指向相同的字符串。

当你使用反射访问一个字符串时,你会得到实际的指针:

Field field = String.class.getDeclaredField("value");
field.setAccessible(true);

因此,更改this将更改持有指向它的指针的字符串,但由于substring()使用新字符串创建了s3,因此它不会更改。

字符串在JVM堆内存的永久区域中创建。是的,它确实是不可变的,在创建之后就不能更改。 因为在JVM中,有三种类型的堆内存: 1. 年轻的一代 2. 老的代 3.永久的一代。

当创建任何对象时,它将进入年轻代堆区域和为字符串池保留的永久代区域。

以下是更多细节,你可以从中获取更多信息: 垃圾收集在Java中的工作原理。

您正在使用反射来访问字符串对象的“实现细节”。不可变性是对象的公共接口的特性。

s3实际上没有更改的原因是,在Java中,当您执行子字符串时,子字符串的值字符数组会在内部复制(使用Arrays.copyOfRange())。

s1和s2是相同的,因为在Java中它们都指向同一个被合并的字符串。这是在Java中设计的。

字符串是不可变的*,但这只意味着你不能使用它的公共API更改它。

您在这里所做的是绕过常规API,使用反射。同样地,你可以改变枚举的值,改变Integer自动装箱中使用的查找表等等。

s1和s2改变值的原因是,它们都指向同一个被合并的字符串。编译器执行此操作(如其他答案所述)。

s3没有这样做的原因实际上让我有点惊讶,因为我认为它会共享值数组(在Java 7u6之前的早期版本中是这样的)。但是,查看String的源代码,我们可以看到子字符串的值字符数组实际上是复制的(使用Arrays.copyOfRange(..))。这就是它不变的原因。

你可以安装一个SecurityManager,避免恶意代码做这样的事情。但是请记住,有些库依赖于使用这种反射技巧(通常是ORM工具,AOP库等)。

*)我最初写的字符串不是真正的不可变,只是“有效的不可变”。在String的当前实现中,这可能会引起误解,其中值数组确实被标记为private final。不过,仍然值得注意的是,在Java中没有办法将数组声明为不可变的,因此必须注意不要在类之外公开它,即使使用适当的访问修饰符。


由于这个话题似乎非常受欢迎,这里有一些建议的进一步阅读:Heinz Kabutz在JavaZone 2009上的Reflection Madness演讲,它涵盖了OP中的许多问题,以及其他的反思……嗯…疯狂。

它涵盖了为什么这有时是有用的。以及为什么,大多数时候,你应该避免它。: -)