目录
一、不可变设计
String是怎么保证不可变的?
首先String类里面有个char数组value,是用final修饰的,所以只有在构造的时候能给他赋值,以后就没机会改变他的引用了。
有个变量hash,用来缓存字符串的哈希码,只有首次调用字符串时才会生成,后面就缓存再变量里避免后面计算,私有的没set方法所以没办法改变。
类上也加了final修饰保证了该类中的方法不能被覆盖,防止系列无意间破坏不可变性
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
// ...
}
final只能保证引用不变,他是怎么保证里面的数据不改变的呢?
看下面代码可见他的构造器是采用复制的方式来构建一个的,防止别的地方改了这个引用对象的值从而改了String(这种思想叫:保护性拷贝)
public String(@NotNull String original){
this.value = original.value;
this.hash = original.hash;
}
public String(@NotNull char value[]){
this.value = Arrays.copyOf(value,value.length);
}
可见在substring的源码也是如此,他返回的时候也是new了个新的来返回,这样substring并没有改动原来的字符串对象
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
这种通过创建副本对象来避免共享的手段称之为保护性拷贝
二、享元模式
由于保护性拷贝每次都会复制对象,就可以用享元模式来解决,他是23种设计模式之一,用在当需要重用数量右下的同一类对象时
JDK中Boolean、Byte、Short、Long、Integer、Character等包装提供了valueOf方法,例如Long的valueOf会缓存-128-127之间的Long对象,在这个范围之间会重用对象,大于这个范围才会新建Long对象
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
是怎么实现这个缓存的呢? 他有个静态内部类LongCache初始化会创建大小为256的数组,然后初始化代码块会事先给他们赋值,之后调用valueOf直接从数组里获取,避免对象的重复创建
注意:Byte、Short、Long的缓存范围都是-128-127,Character缓存的是0-127,Integer的默认是-128-127最小的是不能变,但是这个127是可以调整参数Djava.lang.Integer.integerCache.high来改变。Boolean缓存了true和false
三、final原理
final的设置变量的原理,如果一个成员变量别标识为final,那么他在赋值的时候会加入写屏障,他可以保证写屏障之前的都不会重排序到写屏障之后
获取final变量的原理:如果final的变量是享元缓存范围内的会直接去栈内存中拿,如果是享元缓存外的会去常量池里面拿,如果不在final修饰,他都是在堆中拿