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

我是这样回答的:

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

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

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


当前回答

除了这些精彩的回答,我还想补充几点。像字符串一样,Array保存了对数组开头的引用,所以如果你创建了两个数组arr1和arr2,并做了类似arr2 = arr1的事情,这将使arr2的引用与arr1相同,因此改变其中一个的值将导致另一个的改变

public class Main {
    public static void main(String[] args) {
        int[] a = {1, 2, 3, 4};
        int[] b = a;
        a[0] = 8;
        b[1] = 7;
        System.out.println("A: " + a[0] + ", B: " + b[0]);
        System.out.println("A: " + a[1] + ", B: " + b[1]);
        //outputs
        //A: 8, B: 8
        //A: 7, B: 7
    }
}

Not only that it would cause bugs in the code it also can(and will) be exploited by malicious user. Suppose if you have a system that changes the admin password. The user have to first enter the newPassword and then the oldPassword if the oldPassword is same as the adminPass the program change the password by adminPass = newPassword. let's say that the new password has the same reference as the admin password so a bad programmer may create a temp variable to hold the admin password before the users inputs data if the oldPassword is equal to temp it changes the password otherwise adminPass = temp. Someone knowing that could easily enter the new password and never enter the old password and abracadabra he has admin access. Another thing I didn't understand when learning about Strings why doesn't JVM create a new string for every object and have a unique place in memory for it and you can just do that using new String("str"); The reason you wouldn't want to always use new is because it's not memory efficient and it is slower in most cases read more.

其他回答

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

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

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

我们不能确定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的。

如果HELLO是你的字符串,那么你不能把HELLO改成HILLO。这个性质叫做不可变性。

你可以有多个指针字符串变量指向HELLO字符串。

但是如果HELLO是char Array,那么你可以将HELLO改为HILLO。例如,

char[] charArr = 'HELLO';
char[1] = 'I'; //you can do this

答:

编程语言具有不可变的数据变量,因此可以作为键、值对中的键使用。字符串变量用作键/索引,因此它们是不可变的。

字符串是不可变的Sun微系统,因为字符串可以用来存储在地图集合的关键。 StringBuffer是可变的,这就是它不能在map对象中用作键的原因

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

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

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