我们都知道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的值不变?
[免责声明,这是一个故意固执己见的回答风格,因为我觉得一个更“不要在家里这样做,孩子们”的回答是有保证的]
sin是line field.setAccessible(true);它说通过允许访问私有字段来违反公共API。这是一个巨大的安全漏洞,可以通过配置一个安全管理器来锁定。
The phenomenon in the question are implementation details which you would never see when not using that dangerous line of code to violate the access modifiers via reflection. Clearly two (normally) immutable strings can share the same char array. Whether a substring shares the same array depends on whether it can and whether the developer thought to share it. Normally these are invisible implementation details which you should not have to know unless you shoot the access modifier through the head with that line of code.
依赖这些细节并不是一个好主意,因为如果不违反使用反射的访问修饰符就无法体验这些细节。该类的所有者只支持普通的公共API,并且可以在将来自由地进行实现更改。
Having said all that the line of code is really very useful when you have a gun held you your head forcing you to do such dangerous things. Using that back door is usually a code smell that you need to upgrade to better library code where you don't have to sin. Another common use of that dangerous line of code is to write a "voodoo framework" (orm, injection container, ...). Many folks get religious about such frameworks (both for and against them) so I will avoid inviting a flame war by saying nothing other than the vast majority of programmers don't have to go there.
这里有两个问题:
字符串真的是不可变的吗?
为什么s3没有改变?
第一点:除了ROM,在你的计算机中没有不可变的内存。现在甚至ROM有时也是可写的。总有一些代码(无论是绕过托管环境的内核代码还是本机代码)可以写入内存地址。所以,在“现实”中,它们不是绝对不变的。
对于第2点:这是因为substring可能分配了一个新的字符串实例,这可能是复制数组。substring有可能以不复制的方式实现,但这并不意味着它会这样做。这涉及到权衡。
例如,应该持有对reallyLargeString.substring(reallyLargeString. substring)的引用。长度- 2)导致大量的内存保持活跃,还是只有几个字节?
这取决于substring如何实现。深度复制将保留较少的活动内存,但运行速度会稍微慢一些。浅拷贝可以保留更多的内存,但速度更快。使用深度拷贝还可以减少堆碎片,因为字符串对象及其缓冲区可以分配在一个块中,而不是两个单独的堆分配。
在任何情况下,看起来您的JVM都选择对子字符串调用使用深度拷贝。