1.问题
当你学习Java中的String类时,一定会有人告诉你java中字符串是不可以修改的,也就说在java中字符串一旦创建就不可以修改了。那么我们看下面一个例子。
刚学习String类的同学,肯定觉得上述例子中的s对象,调用了 substring方法,应该本身也会改变,变成123,但实际上s对象还是原来的123-34,那么s对象调用substring方法会发生什么呢,下面我们来解决这个问题,这涉及到String的源码。
2源码解析
这里只介绍部分源码,因为源码内容很多,所以本文只介绍String为什么是不可变对象,以及一些字符串的拼接,截断是如何实现的
/*
首先,我们注意到String类前面有个final修饰符,这个修饰符出现在class前
,代表这个类没有子类,也就是说我们不能通过继承这个类实现 某些操作,
这也保证了我们String类的安全性,也可以说jdk源码的安全性,因为String类其实
是jdk给我们提供的,如果我们继承这个类,可能会通过多态来影响我们代码的安全性
*/
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
/*
1.这个其实就是字符串的底层存储方式,比如说String s = "123";
其实计算机是将1 2 3 这三个字符,转换成对应的字节,并将其存入
value这个字符数组中去
2.这个字符数组前有final关键字,final在变量前代表其是常量,是不可以修改的
但这并不代表这个字符数组就是不可以被修改的,因为在java中,数据类型有两大类
一种是基本数据类型,另一种是引用数据类型,而他们可以在栈中存储,也可以在堆里面
这里不做介绍。你只需知道数组是引用数据类型,它是存储在堆里的,也就是说,final加在
数组变量前只表示这个数组的地址是不会变的,这边有点难理解,意思就是这个数组永远都是
原数组,但里面的内容可以发生改变,因为数组是连续的内存空间,里面的内容变了,
但地址是不变的。
3.这个字符数组前有 private关键字,这个关键字代表这个属性只能在
本类中访问,也就是说我们在其他类使用字符串是拿不到底层的存储结构的
,可以避免对其修改,也就解决了上面 final无法保证数组内容可以会发生改变的问题。
总之, final 和 private关键字,保证了 字符串的内容是不会被改变的,
只需记住这两个关键字保证了字符串的不可变性
*/
@Stable
private final byte[] value;
/*
这里我们只简单看一下字符串的截断,显而易见,它并没有修改源对象
只是把源对象的内容进行截断,并返回一个新的字符串,所以
所有涉及字符串内容改变的方法,都只是返回一个新字符串,只不过这个字符串的
内容是进行相应操作的结果
*/
public String substring(int beginIndex, int endIndex) {
int length = length();
checkBoundsBeginEnd(beginIndex, endIndex, length);
if (beginIndex == 0 && endIndex == length) {
return this;
}
int subLen = endIndex - beginIndex;
return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
: StringUTF16.newString(value, beginIndex, subLen);
}
}