Java字符串真的不可变吗?

我们都知道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更改值的原因是它们都指向相同的实际字符串。 编译器这样做(正如其他答案所述)。

s3的原因实际上让我感到有些惊讶,因为我认为它会共享value数组(在Java 7u6之前的早期版本的Java中)。 但是,查看String的源代码,我们可以看到实际上复制了子字符串的value字符数组(使用Arrays.copyOfRange(..) )。 这就是为什么它没有改变。

您可以安装SecurityManager ,以避免恶意代码执行此类操作。 但请记住,一些库依赖于使用这些反射技巧(通常是ORM工具,AOP库等)。

*)我最初写道, String不是真正不可变的,只是“有效的不可变的”。 这在String的当前实现中可能会产生误导,其中value数组确实标记为private final 。 但是,值得注意的是,没有办法在Java中声明一个数组是不可变的,所以必须注意不要将它暴露在类之外,即使使用适当的访问修饰符。


由于这个话题看起来非常受欢迎,这里有一些建议进一步阅读:Heinz Kabutz的反思疯狂讲话来自JavaZone 2009,其中涵盖了OP中的许多问题,以及其他反思......以及疯狂。

它涵盖了为什么这有时是有用的。 为什么,大多数时候,你应该避免它。 :-)


在Java中,如果两个字符串基元变量初始化为相同的文字,则它将相同的引用分配给两个变量:

String Test1="Hello World";
String Test2="Hello World";
System.out.println(test1==test2); // true

这就是比较返回真实的原因。 第三个字符串是使用substring()创建的,它创建一个新的字符串而不是指向相同的字符串。

当你使用反射访问一个字符串时,你会得到实际的指针:

Field field = String.class.getDeclaredField("value");
field.setAccessible(true);

所以改为这会改变持有指向它的指针的字符串,但是由于substring()使用新字符串创建s3 ,所以它不会改变。

更改


您正在使用反射来规避String的不变性 - 这是一种“攻击”形式。

有很多你可以创建的例子(例如,你甚至可以实例化一个Void对象),但这并不意味着String不是“不可变的”。

在这种情况下,这种类型的代码可能会被用于您的优势,并且是“良好的编码”,例如在尽可能早的时刻(在GC之前)从内存中清除密码。

根据安全管理器的不同,您可能无法执行代码。

链接地址: http://www.djcxy.com/p/73653.html

上一篇: Is a Java string really immutable?

下一篇: Java += compiler/jre bug?