MST

星途 面试题库

面试题:Java中String类的不可变性在底层是如何实现的?

请详细阐述Java中String类被设计为不可变的原因,以及从底层代码角度说明它是如何保证不可变性的。
25.4万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

String类设计为不可变的原因

  1. 安全性:在多线程环境下,String作为参数传递给方法时,如果可变,一个线程修改了字符串内容,可能会对其他依赖该字符串的线程产生不可预知的影响。例如在网络连接、数据库操作等场景中传递的字符串,如果被意外修改,会导致安全漏洞。比如在使用URL时,其路径部分由字符串构成,如果可变,可能被恶意篡改。
  2. 缓存:由于String不可变,所以可以将字符串缓存起来提高性能。Java中有字符串常量池,当创建字符串时,首先会在常量池中查找是否存在相同内容的字符串,如果存在则直接返回引用。例如String s1 = "hello"; String s2 = "hello";,s1和s2指向常量池中的同一个字符串对象,节省了内存空间。
  3. 哈希值稳定性:String经常被用作HashMap的键,如果字符串可变,其哈希值可能会改变,导致在哈希表中存储和查找出现错误。因为在HashMap中,对象的哈希值在存储时确定,查找时根据哈希值定位,如果哈希值改变就无法正确找到对应的值。

从底层代码角度保证不可变性

  1. 存储结构:在Java 9之前,String类内部是通过char数组存储字符串内容,private final char value[]final关键字保证了数组引用一旦初始化就不能再指向其他数组,防止外部代码修改数组引用。在Java 9及之后,改为使用byte数组存储,private final byte[] value,同时增加了一个coder字段来标识编码方式。
  2. 方法实现String类的方法,如substringreplace等,这些方法看似修改了字符串内容,但实际上都是创建并返回一个新的String对象,原字符串对象并没有改变。例如substring方法:
public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndex - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return ((beginIndex == 0) && (endIndex == value.length))? this
            : new String(value, beginIndex, subLen);
}

这里返回了一个新的String对象,而不是修改原对象。同样,replace方法也是如此,它遍历原字符串,根据替换规则构建新的字符串返回。 3. 没有提供修改内部数组的方法String类没有对外提供直接修改内部存储数组的方法,进一步保证了不可变性。外部代码无法直接访问和修改存储字符串内容的数组,只能通过String类提供的方法操作字符串,而这些方法都是返回新的字符串对象。