String 为什么是不可变的?源码解析

237 阅读3分钟

一、String 不可变的定义

1. 不可变对象的定义

  • 不可变对象:一旦创建,其状态(属性值)不能被修改。
  • String 的不可变性:String 对象一旦创建,其字符序列不能被修改。

二、String 不可变的源码解析

1. String 类的定义

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    // 存储字符序列的数组
    private final char value[];

    // 缓存字符串的哈希值
    private int hash; // Default to 0

    // 其他字段和方法...
}

关键点

  • final 类:String 类不能被继承。
  • final char value[]:存储字符序列的数组是 final 的,引用不可变。

2. 字符串的创建

public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

关键点

  • 新创建的 String 对象直接引用原字符串的 value 数组,而不是复制一份。

3. 字符串的修改操作

substring 方法

public String substring(int beginIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value.length - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

关键点

  • 如果 beginIndex 为 0,直接返回原字符串(引用相同)。
  • 否则,创建一个新的 String 对象。

concat 方法

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}

关键点

  • 创建一个新的字符数组 buf,复制原字符串和新字符串的内容。
  • 返回一个新的 String 对象。

4. 字符串的哈希值缓存

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

关键点

  • 哈希值在第一次计算后被缓存,因为字符串不可变,哈希值不会改变。

三、String 不可变的好处

1. 安全性

  • 不可变对象是线程安全的:多个线程可以共享不可变对象,无需同步。
  • 防止篡改:字符串作为参数传递时,不会被意外修改。

2. 性能优化

  • 哈希值缓存:字符串的哈希值只需计算一次,适合作为哈希表的键。
  • 字符串池:不可变性使得字符串池(String Pool)成为可能,节省内存。

3. 设计简化

  • 简化 API 设计:不可变对象的行为更可预测,减少错误。

四、String 不可变的实现原理

1. final 关键字

  • final 修饰的类不能被继承,防止子类破坏不可变性。
  • 字段final 修饰的字段引用不可变,防止被重新赋值。

2. 私有字段

  • value 数组private final char value[],外部无法直接访问或修改。

3. 防御性拷贝

  • 构造函数:在创建新字符串时,复制字符数组,防止外部修改。

五、代码示例

1. 字符串的不可变性

String str1 = "Hello";
String str2 = str1.concat(" World");
System.out.println(str1); // 输出:Hello
System.out.println(str2); // 输出:Hello World

解释

  • str1 的值没有被修改,concat 方法返回了一个新的字符串。

2. 字符串池

String str3 = "Hello";
String str4 = "Hello";
System.out.println(str3 == str4); // 输出:true

解释

  • str3 和 str4 引用字符串池中的同一个对象。

六、Summary

  • String 不可变的原因

    • final 类和字段。
    • 私有字符数组。
    • 防御性拷贝。
  • 不可变的好处

    • 线程安全。
    • 性能优化(哈希值缓存、字符串池)。
    • 设计简化。

通过理解 String 不可变的源码和设计原理,开发者可以更好地利用字符串的特性,编写高效、安全的代码。