在一次采访中,有人问我为什么String是不可变的

我是这样回答的:

当我们在java中创建一个字符串,如string s1="hello";然后一个 对象将在字符串池(hello)中创建,s1将 指着你好。现在如果我们再次执行String s2="hello";然后 不会创建另一个对象,但s2将指向hello 因为JVM将首先检查相同的对象是否在 是否为字符串池。如果不存在,则只创建一个新的,否则不存在。

现在如果假设java允许字符串可变,那么如果我们将s1改为hello world,那么s2值也将是hello world,所以java字符串是不可变的。

谁能告诉我我的答案是对的还是错的?


当前回答

String类是FINAL,这意味着你不能创建任何类来继承它,改变基本结构,使Sting可变。

另一件事,实例变量和String类的方法是这样的,你不能改变String对象一旦创建。

您所添加的内容并没有使字符串成为不可变的。这些都说明了字符串是如何存储在堆中的。字符串池在性能上也有巨大的差异

其他回答

String类是FINAL,这意味着你不能创建任何类来继承它,改变基本结构,使Sting可变。

另一件事,实例变量和String类的方法是这样的,你不能改变String对象一旦创建。

您所添加的内容并没有使字符串成为不可变的。这些都说明了字符串是如何存储在堆中的。字符串池在性能上也有巨大的差异

这可能与安全性没有多大关系,因为安全实践建议使用字符数组作为密码,而不是字符串。这是因为当不再需要数组时,可以立即删除数组。不同的是,字符串不能被擦除,因为它是不可变的。在垃圾收集之前可能需要很长时间,在内容被覆盖之前可能需要更长的时间。

我认为选择不变性是为了允许共享字符串,它们很容易分裂。字符串赋值,选择子字符串变成了一个常数时间的操作,字符串比较也是,因为可重用的哈希码是字符串数据结构的一部分,可以首先进行比较。

从另一方面来说,如果原始字符串很大(比如大型XML文档),那么从中选取少量符号可能会防止整个文档被垃圾收集。因此,后来的Java版本似乎远离了这种不变性。现代c++既有可变(std::string)版本,从c++ 17开始也有不可变(std::string_view)版本。

根据DZone的这篇文章,最重要的原因是:

字符串常量池 ... 如果字符串是可变的,用一个引用更改字符串将导致其他引用得到错误的值。 安全 字符串被广泛用作许多java类的参数,例如网络连接,打开文件等。如果字符串不是不可变的,则连接或文件将被更改,从而导致严重的安全威胁。 ...

希望对你有所帮助。

字符串是不可变的有几个原因,这里是一个总结:

Security: parameters are typically represented as String in network connections, database connection urls, usernames/passwords etc. If it were mutable, these parameters could be easily changed. Synchronization and concurrency: making String immutable automatically makes them thread safe thereby solving the synchronization issues. Caching: when compiler optimizes your String objects, it sees that if two objects have same value (a="test", and b="test") and thus you need only one string object (for both a and b, these two will point to the same object). Class loading: String is used as arguments for class loading. If mutable, it could result in wrong class being loaded (because mutable objects change their state).

也就是说,String的不可变性只是意味着你不能使用它的公共API改变它。实际上,您可以使用反射绕过常规API。在这里看到答案。

在你的例子中,如果String是可变的,那么考虑下面的例子:

  String a="stack";
  System.out.println(a);//prints stack
  a.setValue("overflow");
  System.out.println(a);//if mutable it would print overflow

我们不能确定Java设计师在设计字符串时实际上在想什么,但我们只能根据字符串不可变性所带来的优势来总结这些原因,其中一些是

1. 字符串常量池的存在

正如在为什么字符串存储在字符串常量池文章中所讨论的,每个应用程序都会创建太多的字符串对象,为了避免JVM首先创建大量的字符串对象,然后再对它们进行垃圾收集。JVM将所有字符串对象存储在一个称为string常量池的单独内存区域中,并重用该缓存池中的对象。

每当我们创建一个字符串字面值时,JVM首先查看该字面值是否已经存在于常量池中,如果存在,新的引用将开始指向SCP中的相同对象。

String a = "Naresh";
String b = "Naresh";
String c = "Naresh";

在上面的例子中,值为Naresh的字符串对象将只在SCP中创建一次,所有引用a, b, c将指向同一个对象,但如果我们尝试在例如a.replace("a", "")中进行更改呢?

理想情况下,a应该具有值Nresh,但b和c应该保持不变,因为作为最终用户,我们只在a中进行更改。我们知道a b c都指向同一个对象所以如果我们对a做了改变,其他的也应该反映这个变化。

但是字符串不变性将我们从这种情况中拯救出来,由于字符串对象的不变性,字符串对象Naresh将永远不会改变。因此,当我们在字符串对象中进行任何更改时,Naresh JVM创建一个新对象,将其分配给a,然后在该对象中进行更改。

所以String池是唯一可能的,因为String的不可变性,如果String不是不可变的,那么缓存字符串对象和重用它们将是不可能的,因为任何变量都会改变值并损坏其他变量。

这就是为什么JVM非常特别地处理它,并给它一个特殊的内存区域。

2. 线程安全

当多个线程对一个对象进行操作,但没有一个线程能够破坏它的状态,并且对象在任何时间点对每个线程保持相同的状态时,该对象被称为线程安全的。

不可变对象在创建后不能被任何人修改,这使得每个不可变对象在默认情况下都是线程安全的。我们不需要对它应用任何线程安全措施,比如创建同步方法。

因此,由于其不可变的性质,字符串对象可以由多个线程共享,即使它被许多线程操纵,它也不会改变其值。

3.安全

在每个应用程序中,我们需要传递一些秘密,例如用户的用户名\密码,连接url,通常,所有这些信息都是作为字符串对象传递的。

现在假设如果String在本质上不是不可变的,那么它将对应用程序造成严重的安全威胁,因为这些值被允许更改,如果允许更改,那么这些值可能会由于错误编写的代码或任何其他有权访问变量引用的人而更改。

4. 类加载

正如在Java中通过反射创建对象示例中所讨论的,我们可以使用class . forname ("class_name")方法在内存中加载一个类,该方法再次调用其他方法来执行此操作。甚至JVM也使用这些方法来加载类。

但如果你清楚地看到,所有这些方法都接受类名作为字符串对象,所以字符串用于java类加载和不可变性提供了安全,正确的类被ClassLoader加载。

假设如果String不是不可变的我们试图加载java。lang。object它会变成org。theft。ouobject现在我们所有的对象都有一个行为可以用来做不想要的东西。

5. HashCode缓存

如果我们要在任何对象上执行任何与哈希相关的操作,我们必须重写hashCode()方法,并尝试通过使用对象的状态来生成准确的哈希码。如果一个对象的状态正在改变,这意味着它的hashcode也应该改变。

因为String是不可变的,所以一个字符串对象持有的值永远不会改变,这意味着它的hashcode也不会改变,这给了String类一个机会在对象创建期间缓存它的hashcode。

是的,String对象在对象创建时缓存了它的hashcode,这使得它成为哈希相关操作的最佳候选者,因为hashcode不需要再次计算,这为我们节省了一些时间。这就是为什么String主要用作HashMap键的原因。

阅读更多关于为什么字符串在Java中是不可变的和Final的。