什么是不可变
不可变对象是指一个对象的状态在对象被创建之后就不再变化,包括对象内的成员变量、基本数据类型的值等等。String为什么不可变
String类本质都是对字符数组的封装(而且value也只是一个引用,它指向一个真正的数组对象)。该数组value被声明为私有,外部便无法直接访问该成员,并且加以final修饰 , 源码中也没有提供value的set方法,因此只能对value赋值一次,所以String类一旦初始化,外部便无法修改,因为value被修饰为 private final,在String类内部也无法改变。所以String对象是不可变的。String的源码(JDK1.8)
public final class String //将类声明为final,所以它不能被继承
implements java.io.Serializable, Comparable<String>, CharSequence {
/** 将value数组声明为private 和final的,这样内外均无法改变该成员 */
private final char value[];
/** 哈希值 */
private int hash; // Default to 0
.....
}
- 看似改变String的方法
在我们使用String对象提供的substring、replace、replaceAll、toLowerCase等方法的时候,给我们感觉确实改变了String的值,看一下以下代码
String s = "ABC";
s = s.replace('B', 'A');
System.out.println("s = " + s); //输出AAC
String s = "ABCD";
s.replace('B', 'A');
System.out.println("s = " + s); //输出ABCD
从结果中可以看出执行方法后应该是创建了一个新的String对象并返回,所以第一个输出s已经replace方法执行后返回的新的String对象,在看一下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 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);//替换完成后返回一个新的String对象
}
}
return this;
}
String这些方法的内部其实在完成逻辑后创建了一个新的String对象并返回。这也是String,StringBuffer,StringBuilder,的区别,他们虽然都是final类,不允许被继承,在本质上都是字符数组,但是不同的是,String的长度是不可变的而后两者长度可变,在进行连接操作时,String每次返回一个新的String实例,而StringBuffer和StringBuilder的append方法直接返回this,所以当进行大量的字符串连接操作时,不推荐使用String,因为它会产生大量的中间String对象。
- String类真的不可变吗
从上文可知String的成员变量value是private final修饰的,初始化后不可再改变并且不能再指向其他数组对象。但是value本身是一个引用变量,而不是真正的对象,那么我能改变value指向的数组吗? 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组。
那么只要可以访问私有成员,我们就可以改变它。那么反射便可以解决, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构
下面是实例代码:
//创建字符串"Hello World", 并赋给引用s
String s = "Hello World";
System.out.println("s = " + s); //Hello World
//获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
//改变value属性的访问权限
valueFieldOfString.setAccessible(true);
//获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(s);
//改变value所引用的数组中的字符
value[5] = '_';
System.out.println("s = " + s); //Hello_World