我们都知道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的值不变?
可见性修饰符和final(即不可变性)不是针对Java中的恶意代码的度量;它们仅仅是防止出现错误并使代码更具可维护性的工具(这是系统的一大卖点)。这就是为什么您可以通过反射访问内部实现细节,例如字符串的支持字符数组。
你看到的第二个效果是所有的字符串都改变了,而看起来你只改变了s1。它是Java字符串字面量的一个特定属性,它们被自动存储,即缓存。两个具有相同值的String字面值实际上是同一个对象。当你用new创建一个String时,它不会自动被实习生,你也不会看到这个效果。
#substring until recently (Java 7u6) worked in a similar way, which would have explained the behaviour in the original version of your question. It didn't create a new backing char array but reused the one from the original String; it just created a new String object that used an offset and a length to present only a part of that array. This generally worked as Strings are immutable - unless you circumvent that. This property of #substring also meant that the whole original String couldn't be garbage collected when a shorter substring created from it still existed.
在当前的Java和当前版本的问题中,#substring没有奇怪的行为。