我们都知道String在Java中是不可变的,但请检查以下代码:
String s1=“你好世界”;
String s2=“你好世界”;
字符串s3=s1。子字符串(6);
系统输出打印LN(s1);//你好,世界
系统输出打印LN(s2);//你好,世界
System.out.println(s3);//世界
Field=String.class.getDeclaredField(“值”);
字段。setAccessible(true);
char[]value=(char[])field.get(s1);
值[6]='J';
值[7]=“a”;
值[8]='v';
值[9]=“a”;
值[10]='!';
系统输出打印LN(s1);//你好,Java!
系统输出打印LN(s2);//你好,Java!
System.out.println(s3);//世界
为什么这个程序会这样运行?为什么s1和s2的值改变了,而不是s3
String是不可变的*但这只意味着您不能使用其公共API对其进行更改
您在这里所做的是绕过普通API,使用反射。同样,您可以更改枚举的值,更改整数自动装箱中使用的查找表等
现在,之所以s1和s2更改值,是因为它们都引用相同的内部字符串。编译器会这样做(如其他答案所述)
s3doesnot的原因实际上让我有点惊讶,因为我认为它会共享值数组(它在Java 7u6之前的早期版本中就已经出现了。但是,查看字符串的源代码,我们可以看到子字符串的值字符数组实际上已被复制(使用数组.copyOfRange(..))。这就是为什么它没有改变
您可以安装SecurityManager,以避免恶意代码执行此类操作。但是请记住,有些库依赖于使用这些反射技巧(通常是ORM工具、AOP库等)
*)我最初写道,Strings并不是真的不可变的,只是“有效的不可变”。在字符串的当前实现中,这可能会产生误导,其中值数组实际上被标记为私有final。不过,仍然值得注意的是,在Java中无法将数组声明为不可变的,因此必须注意不要将其暴露在类之外,即使使用了适当的访问修饰符
由于这个话题似乎非常受欢迎,这里有一些建议进一步阅读:海因茨·卡布茨的《反思疯狂之谈》来自JavaZone 2009,涵盖了OP中的许多问题,以及其他反思。。。好疯狂
它涵盖了为什么这有时是有用的。为什么,大多数时候,你都应该避免它。:-)