为了账号安全,请及时绑定邮箱和手机立即绑定

Java字符串真的是不可变的吗?

/ 猿问

Java字符串真的是不可变的吗?

HUX布斯 2019-08-14 16:36:17

Java字符串真的是不可变的吗?

我们都知道StringJava 中是不可变的,但请检查以下代码:


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?


查看完整描述

3 回答

?
湖上湖

String 是不可变的*但这仅表示您无法使用其公共API更改它。

你在这里做的是使用反射来规避正常的API。同样,您可以更改枚举值,更改整数自动装箱等中使用的查找表。

现在,原因s1s2变化值是,它们都引用相同的实习字符串。编译器执行此操作(如其他答案所述)。

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

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

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


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

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


查看完整回答
反对 回复 2019-08-14
?
繁星点点滴滴

在Java中,如果将两个字符串原语变量初始化为同一个字面值,则会为这两个变量分配相同的引用:

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

https://img1.mukewang.com/5d53c82f00013a2707480168.jpg

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

https://img2.mukewang.com/5d53c83100016e0a05090158.jpg

使用反射访问字符串时,您将获得实际指针:

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

因此,更改为将更改包含指向它的指针的字符串,但由于s3它创建的新字符串substring()不会更改。

https://img3.mukewang.com/5d53c83400016cd607580331.jpg


查看完整回答
反对 回复 2019-08-14
?
蝴蝶刀刀

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

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

有些用例可能会使用这种类型的代码,并且可以“良好编码”,例如尽早清除内存中的密码(GC之前)。

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


查看完整回答
反对 回复 2019-08-14

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信