我们都知道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的值不变?


当前回答

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

其他回答

字符串本质上是不可变的,因为没有方法来修改字符串对象。 这就是他们引入StringBuilder和StringBuffer类的原因

字符串不可变性是从接口的角度来看的。您使用反射绕过接口,直接修改String实例的内部结构。

s1和s2都被改变了,因为它们都被分配给了同一个“intern”String实例。您可以从这篇关于字符串相等和实习的文章中找到更多关于这部分的内容。您可能会惊讶地发现,在示例代码中,s1 == s2返回true!

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

在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,因此它不会更改。

String是不可变的,但是通过反射你可以改变String类。您只是实时地将String类重新定义为可变的。如果需要,可以将方法重新定义为公共的、私有的或静态的。