前言

看到一篇文章介绍Java中的String为什么是不可变的,挺详细的,尝试翻译、翻译。原文地址:Why String is Immutable or Final in Java

另外,这个网站的文章质量也不错

正文

String在Java中是不可变的,因为String对象缓存在常量池中。由于常量池的字符串可能被多个客户端之间引用,这存在风险问题,当一个客户端发生操作会影响其它客户端。例如,如果一个客户端把String类型的”Test”改为”TEST”,其它所有的客户端也会看到这个值,如开始所解释的那样。由于性能原因,字符串对象的缓存很重要,所以通过是字符串类不可变来避免这种风险。同时,String也是被final修饰的,因此没有人能够通过继承和重写行为来破坏字符串类的不变性,例如缓存、hashcode计算等等。String类不可变的另一个原因是会导致HashMap无效。

由于用String作为key在HashMap中大量使用,所以不可变对它们来说是非常重要的,这样它们就可以检索存在HashMap中的value对象。因为HashMap的工作原来的hash,它要求hash值是可用的,如果在插入后修改了字符串的内容,则可变字符串在插入和检索时将生成两个不同的hashcode,这可能会丢失HashMap中的value对象。

跳过一些不是很重要的内容

正如前面所说,这个问题可能有很多的答案,而String类的设计者可以很自信的回答和这个问题。我期待在Joshua Bloch’s 的《Effective Java》一书中找到一些原因,但是他没有提到。我认为以下几个原因可以解释为什么在Java中String类是不可变的或用final定义。

1)假设常量池没有让String不可变,这是完全不可能的,因为在常量池中,一个String对象/文字,例如“test”已经被许多引用变量引用,因此,如果其中任何一个更改了值,其他人将自动受到影响,例如:

  1. String A = "Test"
  2. String B = "Test"

现在调用 B “Test”..toUpperCase(),它将同一个对象改为”TEST”,异常A也将是”TEST”,这是不可取的,这里有一个很好的图,显示了如何在堆内存和常量池中创建字符串。

image.png

2)String已经被广泛用作Java中的参数,例如打开网络连接时,可以将主机名和端口作为字符串传递,打开数据库连接时可以将数据库URL作为字符串传递,在Java中打开任何文件,可以将文件名作为参数传递给文件I/O类。

假设String不是不可变的,这会导致严重的安全威胁,我的意思是某人可以访问他有权访问的任何文件,然后可以有意或无意的更改文件名并获得该文件的访问权。因为不可变的特性,你不需要担心那种类型的威胁。这个原因还与Java中String的final有关,Java 设计者确保没有任何行为可以重写String类的行为。(ps:这个没看懂)

3)由于String是不可变的,他可以在多个线程之间安全的共享,这对于多线程编程和避免Java中任何同步问题都非常重要。不可变特性也让Java中String实例是线程安全的,这意味着你不需要额外的同步String相关操作。另一个需要注意的点是子字符串会导致内存泄漏,这不是线程相关问题,但确实需要注意的。

4)String在Java不可变的另一个原因是允许String缓存其hashcode,Java中不可变字符串缓存其hashcode,并且不再每次调用String的hashcode方法时进行计算,这使得在Java中使用HashMap的key非常快。简而言之,因为字符串是不可变的,所以一旦创建了字符串的hashcode,,任何人都不能更改它的内容,从而保证多次调用时字符串的hashcode是相同的。

5)Dan Bergh Johnsson在评论中提到的Java中String不可变的另一个原因是它的类加载机制,这具有巨大而根本的安全。如果String是可变的,加载”java.io.Write”的请求会变成”mil.vogoon.DiskErasingWriter”。

跳过一些不是很重要的内容

总结

String类型不可变的原因:

  1. 在常量池中一个字符串可能被多个变量引用,任何一个变量引用改变了值其它变量都会受影响。
  2. 安全问题
  3. 线程安全的
  4. 不可变hashcode就不会变,在HashMap中String类型被大量使用,不会导致检索不到key

请你相信我所说的都是错的