面试题答案
一键面试String类保证不可变性的底层实现
-
字段修饰
String
类被声明为final
,这意味着它不能被继承。源码如下:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { // 类的其他部分 }
- 其内部用于存储字符串数据的字符数组
value
也被声明为final
:
private final char value[];
这确保了一旦数组被初始化,就不能再引用其他数组,即不能通过重新赋值
value
来改变字符串内容。 -
方法实现
- 构造方法:在
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生态系统的影响
- 安全性
- 在涉及敏感信息(如密码)时,
String
的不可变性保证了数据的安全性。因为String
对象一旦创建就不可更改,恶意代码无法通过修改内存中的字符串来获取敏感信息。例如,在JDBC
连接中传递的用户名和密码等敏感信息以String
形式存在,其不可变性防止了这些信息被篡改。
- 在涉及敏感信息(如密码)时,
- 性能优化
- 字符串常量池:由于
String
的不可变性,Java可以实现字符串常量池。相同内容的String
对象在常量池中只保存一份,避免了重复创建相同内容的字符串,节省内存空间。例如:
String s1 = "hello"; String s2 = "hello"; System.out.println(s1 == s2); // 输出true,因为s1和s2引用常量池中的同一个对象
- 缓存HashCode:
String
类的不可变性使得其hashCode
值可以被缓存。因为字符串内容不会改变,其hashCode
值也不会改变,所以第一次计算后可以缓存起来,提高了hashCode
方法的调用效率。这在HashMap
等依赖hashCode
的集合类中使用String
作为键时,大大提高了性能。
- 字符串常量池:由于
- 多线程安全
- 不可变的
String
对象天然是线程安全的。多个线程可以共享同一个String
对象而无需额外的同步机制,因为其内容不会被修改。这在多线程环境下,使用String
作为共享数据时,简化了编程模型,提高了程序的并发性能。例如,在一个多线程的Web应用中,多个线程可能同时读取一个String
类型的配置信息,由于String
的不可变性,无需担心数据一致性问题。
- 不可变的