什么是不可变
String 对象的不可变指的是,任何对于 String 对象的操作不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。
为什么是不可变的
Java 关于 String 的源码如下:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本类型则初始化后不能被改变,修饰的变量是引用类型初始化后不能再指向其它的对象。
String 类是用关键字 final 修饰的,使得 String 类不可被继承,进而避免了破坏 String 类的不可变。
成员变量 value 是用 final 修饰的,保证了 value 的引用地址不可变,但是里面的具体元素是可以被改变的。看下面这个例子:
final int[] value = {1,2};
int[] another = {4,5,6};
value = another; // 编译报错,final 不可变
编译器不允许我们直接讲 value 指向堆区的另一个地址,但是如果我们直接对数组元素动手,是可以修改 value 里的元素值的。
final int[] value = {1,2};
value[1] = 3;
所以,String 不可变的真正原因是:
- 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
- String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
不可变的好处与坏处
线程安全性(好处)
在并发场景下,多个线程读取同一个资源,是不会引发竟态条件的。只有在对资源进行些操作时才有危险。不可变对象不能被修改,所以是线程安全的。
节省空间,提高效率(好处)
String 还有字符串常量池的属性, one 和 two 两个变量指向的是同一个地址。在大量使用字符串的情况下,可以节省内存空间,提高效率。
String 的不可变条件是必要条件,要是内存啦字符串内容能够改来改去,这么做就完全没有意义。
修改性能不高(坏处)
由于 String 的不可变性,每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
为什么引入 StringBuilder,StringBuffer
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 final 和 private 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的操作,如 expandCapacity、append、insert、indexOf 等公共方法。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
// append 方法
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
//...
}
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。