MST

星途 面试题库

面试题:从源码和底层实现角度深入剖析Java String不可变性原理

深入到Java源码层面,分析String类是如何通过底层实现保证其不可变性的,包括字段修饰、方法实现等,并且探讨这种实现方式对整个Java生态系统的影响。
29.8万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

String类保证不可变性的底层实现

  1. 字段修饰

    • String类被声明为final,这意味着它不能被继承。源码如下:
    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        // 类的其他部分
    }
    
    • 其内部用于存储字符串数据的字符数组value也被声明为final
    private final char value[];
    

    这确保了一旦数组被初始化,就不能再引用其他数组,即不能通过重新赋值value来改变字符串内容。

  2. 方法实现

    • 构造方法:在String类的构造方法中,对传入的字符数组进行了防御性拷贝。例如:
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    

    这防止了外部代码通过修改传入的字符数组来间接修改String对象的内容。

    • 字符串拼接方法:如concat方法,它不会修改原String对象,而是创建一个新的String对象。
    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);
    }
    

    这里通过创建新的字符数组并拷贝原字符串和拼接字符串的内容,返回新的String对象,原String对象保持不变。

    • 修改字符序列相关方法:像replace方法,同样是返回新的String对象。
    public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */
            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar)? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }
    

    只有当需要替换字符时才创建新的字符数组和String对象,否则返回原String对象,保证原字符串不变。

对Java生态系统的影响

  1. 安全性
    • 在涉及敏感信息(如密码)时,String的不可变性保证了数据的安全性。因为String对象一旦创建就不可更改,恶意代码无法通过修改内存中的字符串来获取敏感信息。例如,在JDBC连接中传递的用户名和密码等敏感信息以String形式存在,其不可变性防止了这些信息被篡改。
  2. 性能优化
    • 字符串常量池:由于String的不可变性,Java可以实现字符串常量池。相同内容的String对象在常量池中只保存一份,避免了重复创建相同内容的字符串,节省内存空间。例如:
    String s1 = "hello";
    String s2 = "hello";
    System.out.println(s1 == s2); // 输出true,因为s1和s2引用常量池中的同一个对象
    
    • 缓存HashCodeString类的不可变性使得其hashCode值可以被缓存。因为字符串内容不会改变,其hashCode值也不会改变,所以第一次计算后可以缓存起来,提高了hashCode方法的调用效率。这在HashMap等依赖hashCode的集合类中使用String作为键时,大大提高了性能。
  3. 多线程安全
    • 不可变的String对象天然是线程安全的。多个线程可以共享同一个String对象而无需额外的同步机制,因为其内容不会被修改。这在多线程环境下,使用String作为共享数据时,简化了编程模型,提高了程序的并发性能。例如,在一个多线程的Web应用中,多个线程可能同时读取一个String类型的配置信息,由于String的不可变性,无需担心数据一致性问题。