前些天面试遇到一个很是难的关于String的问题,“String为什么被设计为不可变的”?相似的问题也有“String为什么被设计为final?”我的认为仍是前面一种问法更准确,设计成final仅仅保证了String类不能被继承,而Immutable相对于final要严格的多。
html
下文主要翻译自:http://java67.blogspot.sg/2014/01/why-string-class-has-made-immutable-or-final-java.htmljava
要回答这个问题,java程序员必须对String是如何工做的,它的特性是什么,还有一些关键原则有一个很深入的理解。String类在Java里是一个上帝类,它有一些其余类所不具有的特性,好比String字面量存储在常量池,你能够经过操做符“+”来链接多个String。鉴于String类在Java编程里的重要性,Java设计者把它设计为final的,这意味着你不能够继承这个类,一样这也有助于String对象的不可变。
记得在某个地方读到过,有人向Java的建立者James Gosling问过为何要把String类设计成final,但他在安全性方面做了一些回答。有人认为,使类设计成final严重限制了它的进化或扩展能力,James评论说,把类设计成final是Java安全性承诺的关键因素,这样在Java平台里就没有人能够改变它的行为了。
如今回到标题的问题,Java里String为何是不可变的呢?首先能够肯定的是这么设计是有优点的。如今,让咱们思考一下这些优势或特性,这是决定这样设计的缘由。
下面列出5个Java里把String设计为final或者Immutable的缘由:
除了JamesGosling关于安全性的提示以外,我认为如下缘由也说明了为何String在Java中设计成final或Immutable的
1)String常量池
java设计者明白String类将会是全部Java应用中使用最多的类,这也是他们想从设计之初就要优化的缘由。优化方向的一个关键想法是在String常量池中存储String字面量。目标是经过共享来减小临时字符串对象,为了共享,String类必须是不可变的。你不能在一个彼此不互知的双方之间共享可变对象。让咱们以一个假设的例子为例,其中两个引用变量指向同一个字符串对象:
String s1 = "Java";
String s2 = "Java";
如今假如s1的值被改为“C++”,引用变量s2在它不知道的状况下,他的值也变成了“C++”。可是经过把String设计为不可变,上述的共享String字面量成为了可能。总之,要在Java中实现字符串池的关键思想,必须把String类设计成不可变的。
2) 安全性
Java在为每一个级别的服务提供安全环境方面有着明确的目标,而字符串在整个安全方面是相当重要的。 String已被普遍用做许多Java类的参数,例如,打开网络链接时,能够将主机和端口做为字符串传递,在Java中读取文件时,能够将文件和目录的路径做为字符串传递,打开数据库链接时,能够将数据库URL做为字符串传递。若是字符串不是不可变的,用户可能已经受权访问系统中的特定文件,可是在身份验证以后,他能够更改到其余文件的路径,这可能会致使严重的安全问题。相似地,在链接到数据库或网络中的任何其余机器时,可变字符串值可能会形成安全威胁。可变字符串也可能致使反射中的安全性问题,由于参数是字符串。
3) 字符串在类加载机制中的应用
另外一个使字符串成为最终或不可变的缘由是由于它在类加载机制中被大量使用。因为字符串不是不可变的,攻击者能够利用这一事实,将加载标准Java类(例如java.io.Reader)的请求更改成恶意类com.un未知n.DataStolenReader。经过保持字符串的最终性和不可变性,咱们至少能够确保JVM正在加载正确的类。
4) 多线程好处
因为并发性和多线程是Java的关键产品,所以考虑字符串对象的线程安全性是颇有意义的。因为预期字符串将被普遍使用,使其不可变意味着没有额外的同步,所以意味着在多个线程之间共享字符串的代码要简单得多。这个单一的特性使得已经很复杂、混乱和容易出错的并发编码变得更加容易。由于字符串是不可变的,并且咱们只是在线程之间共享它,因此它会产生更易读的代码。
5) 优化与性能
如今,当你使类不可变时,您预先知道,这个类一旦建立就不会改变。这保证了许多性能优化的开放思惟,例如缓存。字符串自己知道,我不会更改,因此字符串缓存它的hashcode。它甚至延迟计算hashcode,一旦建立,只需缓存它。在简单的世界中,当您第一次调用任何String对象的hashCode()方法时,它计算哈希代码,而后对hashCode()的全部后续调用都返回已经计算的缓存值。这将带来良好的性能增益,由于字符串在基于哈希的映射(例如Hashtable和HashMap)中大量使用。若是不使其不可变,就不可能缓存哈希代码,由于它取决于字符串自己的内容。
除了上述优势外,您还能够考虑到在Java中字符串是immutable的优势。它是最流行的对象之一,可用做基于哈希集合的键,例如HashMap和Hashtable。虽然对HashMap键来讲,不可变性不是绝对须要的,但使用不可变对象做为键要比使用可变对象安全得多,由于若是可变对象的状态在HashMap中停留期间发生了更改,那么就不可能检索回它,由于它的Eques()和hashCode()方法依赖于已更改的属性。若是一个类是不可变的,那么当它存储在基于哈希的集合中时,就没有改变它的状态的风险,我已经强调的另外一个重要的好处是它的线程安全性。因为字符串是不可变的,因此您能够在线程之间安全地共享它,而没必要担忧额外同步。它使并发代码更具可读性,减小了错误的发生。
尽管有全部这些优势,但不变性也有一些缺点,例如,它并不是没有成本。因为字符串是不可变的,所以它会生成大量临时对象和逃逸对象,这会给垃圾收集器带来压力。Java设计者已经考虑过了,将字符串字面量存储在常量池中是他们减小字符串垃圾的解决方案。这确实有帮助,可是你必须当心地建立字符串而不使用构造函数,例如,new String()不会从字符串池中选择对象。并且,平均来讲,Java应用程序产生的垃圾太多了。此外,将字符串存储在池中有与其相关的隐藏风险。字符串池位于JavaHeap的PermGen空间中,与JavaHeap相比,这是很是有限的。拥有太多的字符串文字会很快占满这个空间,从而导java.lang.OutOfMemoryError。值得庆幸的是,Java语言设计者已经意识到了这个问题,从Java 7开始,他们将字符串池移动到正常的堆空间,这比PermGen空间要大得多。使字符串成为不可变的还有另外一个缺点,由于它限制了它的可扩展性。如今,您不能扩展String来提供更多的功能,虽然不多须要。