为什么字符串在Java中是final或Immutable的5个原因

242 阅读8分钟

几乎没有任何Java面试不问字符串的问题,而为什么字符串在Java中是不可变的,我想这是最受欢迎的Java字符串问题。这个问题也会被问到,为什么Java中的String类是最终的,或者简单地说,为什么String 是最终的。为了回答这些问题,Java程序员必须对String的工作原理、该类的特点、String的内部结构和实现以及一些关键的基本原理有一个坚实的了解。String类是Java中的一个神类,它有其他类所没有的特殊功能,如String 字元存储在字符串池中

您可以使用+运算符连接字符串。考虑到它在Java编程中的重要性,Java设计者将其作为最终对象,这意味着你不能扩展java.lang.String类,这也有助于使String 对象不可变。

现在要问的是,为什么字符串在Java中是不可变的?当然,这应该与好处、优势有关。现在让我们想一想,是什么优势或特点,促使我们做出这个决定。我不知道甲骨文或Sun以前是否有任何官方文件,可以为这个决定提供一些启示。

虽然我记得在某个地方读到过,有一次向Java的创造者James Gosling询问关于将String类变成final的问题,他说了一些安全方面的内容。有人认为,把一个类变成最终类会严重限制其发展或扩展的能力,而James曾评论说,把对Java安全承诺至关重要的类变成最终类,这样就没有人可以改变它的行为和与Java平台的游戏。

为什么Java中的字符串是final或Immutable的5个原因

尽管Java设计者最了解String类被定为最终的真正原因,除了James Gosling对安全的提示外,我认为以下原因也说明了为什么String在Java中是最终或不可变的。

1.字符串池

Java设计者知道,在所有类型的Java应用程序中,字符串 将是最常用的数据类型,这就是为什么他们想从一开始就进行优化。在这个方向上的一个关键步骤是将字符串字头存储在字符串池中的想法。

目的是通过共享来减少临时的String 对象,为了共享,它们必须来自Immutable类。你不能与互不相识的两方共享一个可变的对象。让我们举一个假设的例子,两个引用变量指向同一个String 对象:

String s1 = "Java";
String s2 = "Java";

现在如果s1把对象从"Java "改为"C++",引用变量也得到s2="C++"的值,它甚至不知道这一点。 通过使String不可变,这种String字面的共享就成为可能了。简而言之,如果在Java中不使String final或Immutable,就无法实现String pool的关键思想。

2.2.安全性

Java有一个明确的目标,那就是在每一级服务中提供一个安全的环境,而String在整个安全问题上是至关重要的。字符串已经被广泛地用作许多java类的参数,比如在打开网络连接时,你可以把主机和端口作为字符串传递,在Java中阅读文件时,你可以把文件和目录的路径作为字符串传递,在打开数据库连接时,你可以把数据库的URL作为字符串传递。

如果String不是不可变的,一个用户可能已经授权访问系统中的某个文件,但在认证之后,他可以把PATH改成别的东西,这可能会造成严重的安全问题。

同样,在连接到数据库或网络中的任何其他机器时,突变的String值会带来安全威胁。可变字符串在Reflection中也会造成安全问题,因为参数是字符串。

3.在类加载机制中使用字符串

让String成为最终值或不可变值的另一个原因是,它在类加载机制中被大量使用的事实。由于String不是Immutable,攻击者可以利用这一事实,一个加载标准Java类(如java.io.Reader)的请求可以被改成恶意的com.unknown.DataStolenReader类。通过保持String的最终性和不可变性,我们至少可以确定JVM正在加载正确的类。

4.多线程的好处

由于并发和多线程是Java的主要产品,考虑String对象的线程安全是很有意义的。由于预计String将被广泛使用,使其成为Immutable意味着没有外部同步,这意味着涉及多线程之间共享String的代码更加简洁。

这个单一的特性,使得本来就很复杂、混乱和容易出错的并发编码变得更加容易。因为字符串是不可变的,而且我们只是在线程之间共享它,这导致了更多可读的代码。

5.优化和性能

现在,当你让一个类成为不可变的时候,你事先知道,这个类一旦创建就不会改变。这就保证了许多性能优化的途径,例如缓存。String自己知道我不会改变,所以String缓存了它的哈希码。它甚至懒洋洋地计算哈希码,一旦创建就直接缓存。

在一个简单的世界里,当你第一次调用任何一个字符串对象的hashCode()方法 时,它就会计算出哈希代码,并且所有后续对hashCode() 的调用都会返回已经计算好的、缓存的值。

考虑到String在基于哈希的地图(如HashtableHashMap)中被大量使用,这导致了良好的性能提升。如果不把哈希码变成不可变的和最终的,就不可能对其进行缓存,因为它取决于String本身的内容。

在Java中,字符串是不可变的还是最终的优点和缺点

除了上述好处外,还有一个优势是你可以依靠的,因为Java中的String是最终的。它是最受欢迎的对象之一,可以作为HashMap和Hashtable等基于散列的集合的键。

尽管不可变性不是HashMap 键的绝对要求,但使用不可变对象作为键比使用可变对象要安全得多,因为如果可变对象的状态在HashMap中停留期间发生了变化,那么就不可能将其检索回来,因为其equals()和hashCode()方法取决于变化的属性。

如果一个类是不可变的,那么当它被存储在基于哈希的集合中时,就没有改变其状态的风险。 另一个重要的好处,我已经强调过了,就是它的线程安全。由于String是不可变的,你可以在线程之间安全地分享它,而不必担心外部同步。它使并发的代码更易读,更不容易出错。

尽管有这些优点,Immutability也有一些缺点,比如它并不是没有代价的。由于String是不可变的,它产生了大量的临时使用和抛出对象,这给垃圾收集器带来了压力。Java的设计者已经考虑到了这一点,将String字元存储在池中是他们减少String垃圾的解决方案。

这确实有帮助,但你必须注意在创建String时不使用构造函数,例如new String()不会从String池中挑选一个对象。另外,平均而言,Java应用程序产生了太多的垃圾。另外,在字符串池中存储字符串还有一个隐藏的风险。字符串池位于Java Heap的PermGen Space中,与Java Heap相比,它非常有限。

有太多的字符串字面将很快填满这个空间,导致java.lang.OutOfMemoryError。PermGen Space。值得庆幸的是,Java语言的程序员已经意识到了这个问题,从Java 7开始,他们已经将字符串池移到了正常的堆空间,这比PermGen 空间大得多。

将String设为final还有一个缺点,因为它限制了它的可扩展性。现在,你不能扩展String来提供更多的功能,虽然在一般情况下几乎不需要,但对于那些想扩展java.lang.String类的人来说,还是有限制。

以上就是关于为什么Java中的String是final或Immutable的全部内容。虽然我们不知道确切的原因,因为Oracle从来没有公布过他们在Java中把String类变成final的决定,但这5个实际的原因,即缓存、安全、并发和性能,无疑给了我们一个提示,为什么String类在Java中被变成final和Immutable

当然,这是Java设计者的决定,但看起来以上几点有助于他们做出这个决定。由于类似的原因,像Integer、Long、Double 和Float 等封装类也是不可变和最终的。

您可能想了解的其他Java字符串教程和实例