我们都知道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  

为什么这个程序会这样运行?为什么s1s2的值改变了,而不是s3

推荐答案

String是不可变的,但这只意味着不能使用其公共API更改它.

这里要做的是绕过普通的API,使用反射.同样,您可以更改枚举的值,更改整数自动装箱中使用的查找表等.

现在,s1s2改变值的原因是,它们都引用相同的内部字符串.编译器会这样做(如其他答案所述).

s3not的原因实际上让我有点惊讶,因为我认为它会共享value数组(it did in earlier version of Java,在Java7u6之前).但是,查看String的源代码,我们可以看到子字符串的value字符数组实际上被复制(使用Arrays.copyOfRange(..)).这就是为什么它不变的原因.

你可以安装一个SecurityManager,以避免恶意代码做这样的事情.但请记住,有些库依赖于使用这些反射技巧(通常是ORM工具、AOP库等).

*)我最初写的String并不是真的一成不变,只是"实际上是一成不变的".这在当前的String实现中可能具有误导性,其中value数组实际上被标记为private final.不过,仍然值得注意的是,在Java中无法将数组声明为不可变的,因此必须注意不要将其公开在其类之外,即使使用适当的访问修饰符也是如此.


由于这个话题似乎非常受欢迎,下面是一些建议的进一步阅读:来自JavaZone 2009的Heinz Kabutz's Reflection Madness talk,它涵盖了OP中的许多问题,以及其他反映...好...疯狂.

它解释了为什么这有时很有用.还有为什么,在大多数情况下,你应该避免它.:-)

Java相关问答推荐

使用SaxonJ HE在收件箱中摆脱regex

如果它最终将被转换为int类型,为什么我们在Java中需要较小的integer类型?

如何让TaskView总是添加特定的列来进行排序?

springboot start loge change

Java inline Double条件和值解装箱崩溃

确定Java中Math.Ranb()输出的上限

存根基类的受保护方法

我无法获取我的Java Spring应用程序的Logback跟踪日志(log)输出

如何确定springboot在将json字段转换为Dto时如何处理它?

CompleteableFuture是否运行在不同的内核上?

GSON期间的Java类型擦除

如何从错误通道回复网关,使其不会挂起

为什么Java编译器不区分不同类型的方法?

如何让JVM在SIGSEGV崩溃后快速退出?

%This内置函数示例

删除打印语句会影响功能...腐败在起作用?

Domino中不同的java.Protocol.handler.pkgs设置在XPages Java中导致错误

JavaFX,GridPane:在GridPane的列中生成元素将保持所有列的宽度

PhantomReference无法访问时会发生什么?

如何使用stream.allMatch()为空流返回false?