我们都知道这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?
s1
s2
s3
String 是不可变的*,但这仅意味着你无法使用其公共API对其进行更改。
String
你在这里所做的是使用反射来绕过常规API。同样,你可以更改枚举的值,更改整数自动装箱中使用的查找表等。
现在,原因s1和s2变化值是它们都引用相同的实习字符串。编译器执行此操作(如其他答案所述)。
原因s3实际上并不令我惊讶,因为我认为它可以共享value数组(它在Java的较早版本中(在Java 7u6之前)已完成)。但是,查看的源代码String,我们可以看到value实际上已复制了子字符串的字符数组(使用Arrays.copyOfRange(..))。这就是为什么它保持不变。
value
Arrays.copyOfRange(..)
你可以安装SecurityManager,以避免恶意代码执行此类操作。但是请记住,某些库依赖于使用这些反射技巧(通常是ORM工具,AOP库等)。
*)我最初写道Strings并不是真正不变的,只是“有效的不变”。这可能会在的当前实现中产生误导String,其中value确实标记了数组private final。但是,仍然值得注意的是,没有办法在Java中将数组声明为不可变的,因此即使使用适当的访问修饰符,也必须注意不要将其暴露在类之外。
*)
Strings
private final
由于这个话题似乎非常受欢迎,因此建议你进一步阅读以下内容:Heinz Kabutz在JavaZone 2009上发表的《 Reflection Madness》演讲,其中涵盖了OP中的许多问题以及其他反思……嗯……疯狂。
它涵盖了为什么有时有用。为什么,在大多数情况下,你应该避免使用它。