String 对象是不可变的,字符串是常量,不是变量。我们来分析下为什么Java中String要设计成不可变的。
首先我们来看一段代码:
public class Test {
public static void main(String []args)
{
String s1 = "abcdef";
System.out.println(s1);
s1 = "123456";
System.out.println(s1);
}
}
打印结果:
结果不是变了吗? 为啥还说String是不可变的呢?
s1 仅仅是一个对象引用,并不是对象的本身。对象是在内存中的一块内存区域,而s1是指向这块区域的一个引用。创建对象s1时,s1指向内存中的”abcdef“,当执行了 s1 = "123456"后,s1又指向内存中的”123456“,而”abcdef“并没有改变,还在内存中存在。
String 本身提供了一个replace()方法,不是可以改变内容吗?
public class Test {
public static void main(String []args)
{
String s1 = "abcdef";
System.out.println(s1);
s1.replace("a","A");
System.out.println(s1);
}
}
结果:
字符串并没改变。
改变下写法:
public class Test {
public static void main(String []args)
{
String s1 = "abcdef";
System.out.println(s1);
s1 = s1.replace("a","A");
System.out.println(s1);
}
}
结果:
这样的话,数值就会改变了。s1.replace(“a”,“A”) 会在内存中产生一个新的对象”Abcdef“,s1 = s1.replace(“a”,“A”)执行后,s1指向内存中这块新区域,所以内容改变了,原来的”abcdef“并没有改变,还保存在内存中。
String 是如何实现不可变的呢?
首先看下成员变量:
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
- value数组是用来存放字符的,hash用来存放该对象的哈希值。
- value 对象被private final 修饰,hash 被private修饰,String类并没有对外提供value和hash的set方法,所以外部无法修改。并且通过final关键字保证只要初始话数值了,就无法在改变了。
所以认为String对象是不可改变的。但是value本身又是一个引用,它指向存放真正数据的数组对象。
不可变对象的好处
- 不可变对象更容易构造,测试与使用;
- 真正不可变对象都是线程安全的;
- 不可变对象的使用没有副作用(没有保护性拷贝);
- 对象变化的问题得到了避免;
- 不可变对象的失败都是原子性的;
- 不可变对象更容易缓存,且可以避免null引用;
- 不可变对象可以避免时间上的耦合;
String,StringBuffer,StringBuilder,都是final类,不允许被继承,在本质上都是字符数组。不同的是String长度不可变,其他两个在添加字符串时可以增加。String在每次拼接时都会返回一个新的实例,StringBuffer和StringBuilder 通过append添加数据是在原对象添加。所以在进行大量字符串操作时,不推荐使用String
StringBuffer是线程安全的,因为它在append方法前增加synchronized修饰符,起同步作用,但是效率比StringBuilder 低。
String的这种不可变,我们可以通过其他方法突破的
前面我们提到String类是通过char[] value 来存储的。value本身就是一个引用,虽然不能改变它的引用地址,我们可以修改真正数组数据。因为value是私有,我们通过反射来处理。
public class Test {
public static void main(String []args)
{
try {
String s1 = "abcdef";
System.out.println(s1);
Field valueField = null;
valueField = s1.getClass().getDeclaredField("value");
valueField.setAccessible(true);
char[] value = (char[]) valueField.get(s1);
value[0] = '1';
value[2] = '2';
value[4] = '3';
System.out.println("s = " + s1);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
结果: