转自:深度探秘String类
1 不可变类与可变类的区别?
不可变类:这个类的实例一旦创建完成后,就不能改变其成员变量值。如JDK内部自带的很多不可变类:Interger、Long和String等。
可变类:相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类。
2 怎么设计一个不可变类?
类添加final修饰符,保证类不被继承。
保证所有成员变量必须私有,并且加上final修饰;从而保证了成员变量不可改变。
不提供改变成员变量的方法,包括setter。避免通过其他接口改变成员变量的值,破坏不可变特性。
通过构造器初始化所有成员,进行深拷贝。如果构造器传入的对象直接赋值给成员变量,还是可以通过对传入对象的修改进而导致改变内部变量的值。为了保证内部的值不被修改,可以采用深度copy来创建一个新内存保存传入的值。
在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝。这种做法也是防止对象外泄,防止通过getter获得内部可变成员对象后对成员变量直接操作,导致成员变量发生改变。
3 结合String源码来解释?
下面我们结合String的源码来解释,如下:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
我们可以发现hashCode方法改变了内部状态值int hash;通过String类的hashCode方法实现,我们可以总结:不可变可以改变其内部状态,只要这种改变不要对外暴露。
如果我们重写hashCode方法,如下:
public int hashCode() {
if (hash == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
hash = 31 * hash + val[i];
}
}
return hash;
}
这时,就不是线程安全的。我们移走了本地变量h,直接基于内部状态进行改变操作。如果同时几个线程调用这个方法,则返回值可能不同。
这个类因此是可变的。因为两个不同的线程可以看到不同的hash值。
总结:String是不可改变的,因为它是线程安全的,而不是相反。