Java中的String是一个不可变的类,那么它的不可变性到底体现在哪?是不是因为底层使用final修饰就决定了它是不可变的呢?请带着这些问题一起阅读本文。
String的不可变性体现在哪里?
让我们来看下面这段代码
String s1 = "hello";
s1 = "world";
那么问题来了,是s1指向的内存地址上的值由"hello"变味了"world"了吗?答案:不是的。
上面这张图清楚了展示了整个过程,执行完s1="world"
代码之后,只是改变了s1这个引用地址,不会对原来指向内存地址上的值。
为什么String不可变呢?
让我们来看一下String类中所定义的成员变量
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
private int hash;
private static final long serialVersionUID = -6849794470754667710L;
从上面这些信息可以看出
- String类由final修饰,即不可被继承。
- String 实现了Serializable接口:表示字符串是支持序列化的;实现了Comparable接口:表示String可以比较大小
- 字符串在JDK8版本实际上使用char数据进行存储,而final修饰保证不能被重新赋值,即‘不可变’,那么仅仅靠一个final就解决问题了吗?下面会进行讲解
只靠final就解决问题了吗?
省流:不是的
看下面代码
final char[] value = {'a', 'b', 'c'};
value[2] = 'd';
而此时value中的内容已经变为了a,b,d了,说明final无法保证值不会改变,只是保证了final
修饰的value不能被重新赋值。那么private
就出场了,private首先保证外界是无法修改其值的。那么接下来看看String类内部是否存在修改value
的方法呢?String类内部的方法都是首先将value复制一份,再进行对应操作,最终返回一个新的字符串对象,比如replace、concat方法等,下面以replace方法举例
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[]
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真的不可变吗?
那么用什么方法可以访问到private修饰的value呢?没错,用反射。反射可以拿到String中的value属性,从而可以进行修改。
public void reviseStr() throws Exception {
String s1 = "Hello World";
System.out.println("反射前字符串s1为:" + s1); //Hello World
//1.通过反射拿到String类中的value字段
Field valueField = String.class.getDeclaredField("value");
//2.改变value属性的访问权限
valueField.setAccessible(true);
//3.获取s对象上的value属性的值
char[] value = (char[]) valueField.get(s1);
//4.改变value所引用的数组中的第5个字符
value[5] = ',';
System.out.println("反射后字符串s1为:" + s1); //Hello,World
}