转载

Java的String对象不可修改?

Java字符串是final且不可变的。不过,它的内容可以被修改。

java的生态系统很庞大。大量的新内容始终在产生。紧跟潮流并且记住那些并不会立即使用的东西是很困难的。就我而言,我不停地阅读面试问题,博客以及群组讨论。有时候会碰到我忘了的知识,有时会学习新的知识…有时我看到的答案,嗯,恕我直言,不太准确,或者不完整甚至是错误的。

前一段时间我遇到了这个问题:“为什么java字符串是final且不可变的?”。被推荐的原因是线程安全、性能和安全性。我完全同意第一个原因;如果有像String一样被大量使用的,在多线程中为了安全使用就需要某种同步,这简直是噩梦。

第二个原因我不太确定。《Java并发编程实践》提到了不可变对象有性能优势,但是《Java性能权威手册》的作者指出在现代JVM中,这是不正确的。这两个来源都很可靠,我不知道JVM内部能够为我做出决定。也许不变性 没有直接的优点 。JIT会生成相同的代码,解释器会以相同的方式工作。然而,一个情况是不可变性对垃圾回收机制来说无疑是有利的:不可变对象的容器和 最年轻的对象 一样“年轻”,所以我可以很容易地猜想到minor collection跳过整个容器的垃圾收集(GC)。当然了,更少的对象就意味着更少的时间…所以,字符串容器会被跳过不被扫描到。而且, String 类在Java早期就被这样设计了。也许是因为在某些时候可变对象会有一些性能损失,所以String被这样设计。那么,JVM这样不断发展后,没有理由再改变 String 类了。所以,我假设这个原因是合理的。

但第三个原因—String类设计成不可改变和final是为了安全,这一点对我来说绝对是错误的。这个答案的一个例子是通过类名加载类,类名就是一个String。如果一个黑客可以在类加载器加载字节码之前改变它,那这就是一个安全漏洞。恕我直言这个原因是有漏洞的,因为如果一个黑客可以在你的JVM中运行任意代码,那你可能有一个比String不变性 更大的问题需要“防卫”。另一方面,如果你想搬起石头砸自己脚,谁能阻止你这样做?毕竟这是你的JVM。

有了这个感觉后,我问了自己一个源于“安全假设”的问题:String不可变的设计是否真的意味着一个String对象的内容在其被创建后就不能再被替换了吗?正如你将在下面看到,答案当然是可以。首先,任何东西都可以靠JNI改变。一旦有调用C语言,你将在很多方面打破很多事情我甚至无法数清…所以,我们先把这个放一边,使用纯Java来对一个字符串的内容做修改。这不困难,下面是一个概念证明:

public class StringModifier {     public static void main(String[] str){         try {             String test="aaaa";             String test2 =test;             String test3 = new String(test);             String test4 = new String(test.toCharArray());             Field values = String.class.getDeclaredField("value");              values.setAccessible(true);             char[] ref = (char [])values.get(test);             ref[0] = 'b';              System.out.println("aaaa"+test+" "+test2+" "+test3+" "+test4);         } catch (NoSuchFieldException|SecurityException|             IllegalArgumentException|IllegalAccessException ex) {         }     } }

输出是:

baaabaaa baaa baaa aaaa

它仅仅用了4行代码来修改一个字符串内容。不论这个字符串是在一个常量池里还是分配到了堆上。所有的别名都“看见”了引用对象发生的变化。顺便说,在println()字符串文本的后面加+” ”,就可以使输出变为aaaa baaa baaa aaaa。这种变化发生的原因是java在编译时连接 “aaaa”和” ”,因此在字符串常量池上就有“aaaa”和“aaaa ”。

这个“发现”引发了下面的问题:

这个发现可以利用吗?我想有时是可以的。举个例子,当把敏感信息存储在字符串:如果我们已经不需要这个信息了,还将这个信息保留绝对是糟糕的做法。只有垃圾回收机制能决定什么时候回收一个未引用对象的内存,所以你宝贵的信息将可能比你想象中存在得更久一点。但是一些API需要密码作为方法参数的字符串;有时从环境中传递参数等等。在这种强制使用字符串的情况下,你可以通过修改字符串内容来清理敏感数据…可能还有一些其他的场景需要修改字符串…

另一个要注意的是代码的范围:据我所知,现在有一个关于修改字符串存储值的方式的讨论。大量的字符串使用场景不需要它是unicode。所以在这种情况下建议每个字符使用一个字节。这个字符串类变化很有可能将被引入Java 9,这样上面的代码就不再会起作用了。但是使它适应新的设计也并非那么难。

字符串可以被修改,那么,这些变化将如何体现在多线程环境中?毕竟,字符串的不可变性设计的最重要原因(恕我直言)就是为了多线程正确且一致性行为。有没有可能在不同线程中获得一个字符串对象不同的值?嗯。我想确实有这种情况。如果一个人不使用“正常”的API操作,他就不该指望能得到JVM的保证。我试着用多个线程并发访问和修改字符串,但最终没有得到不同的值。然而我认为差异有时还是会发生的。

这就是我关于java字符串不变性修改的所有想法。阅读愉快。

原文链接: cupofjavasite 翻译:ImportNew.com -LynnShaw

译文链接:[]
原文  http://www.importnew.com/19971.html
正文到此结束
Loading...