简介
String类是Java中的不可变字符串类,这种不可变性主要是因为内部属性private final char value[]字段是用final关键字修饰的,以及没有任何修改char[]的方法。另外,String类也是用final修饰的,说明研发者不希望这个类被继承。学习和面试中也经常会问到String相关知识点。
继承结构
这里可以看出String实现了Serializable、Comparable、CharSequence三个接口。
Compareable主要是用于比较两个字符串的操作
Serializable接口,这是一个序列化标志接口。
CharSequence 接口,表示是一个有序字符的集合。
主要属性
//存储字符串的属性
private final char value[];
//缓存字符串的哈希码
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
可以看出char类型的value值是用final修饰的,底层其实是一个char类型的数组存储字符串的。
String类是用final修饰的,所以我们认为其是不可变对象,但是真的不可变吗?
每个字符串都是由许多单个字符串组成的,value被final修饰,只能保证引用不被改变,但是value所指向的堆中的数组,才是真实的数据,只要能够操作堆中的数组,依旧能够改变数据。而且value是基本类型构成的,那么即使被声明为private,我们也可以通过反射来改变。
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException
{
String str = "hello";
System.out.println(str);
Field fieldStr = String.class.getDeclaredField("value");
fieldStr.setAccessible(true);
char[] value = (char[])fieldStr.get(str);
value[0] = 'H';
System.out.println(str);
}
通过前后两次打印的结果,我们可以看到String被改变了,但是在代码里,几乎不会使用反射的机制去操作String字符串,所以,我们会认为String类型是不可变的。
那么,String类为什么要设计成不可变的呢?我们可以从性能以及安全方面来考虑:
安全
引发安全问题,譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
保证线程安全,在并发场景下,多个线程同时读写资源时,会引竞态条件,由于 String 是不可变的,不会引发线程的问题而保证了线程。
HashCode,当 String 被创建出来的时候,hashcode也会随之被缓存,hashcode的计算与value有关,若 String 可变,那么 hashcode 也会随之变化,针对于 Map、Set 等容器,他们的键值需要保证唯一性和一致性,因此,String 的不可变性使其比其他对象更适合当容器的键值。
性能
当字符串是不可变时,字符串常量池才有意义。字符串常量池的出现,可以减少创建相同字面量的字符串,让不同的引用指向池中同一个字符串,为运行时节约很多的堆内存。若字符串可变,字符串常量池就失去了意义,基于常量池的String.intern()方法也失效,每次创建新的String将在堆内开辟出新的空间,占据更多的内存。
Java没有内置的字符串类型,标准的java类库中提供了一个预定义类,每个用双括号括起来的字符串都是String类的一个实例。与大多数程序设计语言一样,Java语言使用+号拼接两个字符串。当一个字符串与一个非字符串的值进行拼接时,后者被转换成字符串。每次拼接都会创建一个新对象,也就是每次都要去申请内存空间,因为做大量字符串拼接时性能很差,只适合做少量的字符串拼接。
构造方法
String类中提供了很多重载的构造方法,这里只分析一些常用的构造方法。
//无参的构造
public String() {
this.value = "".value;
}
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
length()方法
返回字符串的长度(也就是对应的char数组的长度)
public int length() {
return value.length;
}
isEmpty()方法
判断字符串是否为null (也就是判断char数组长度是否为0)
public boolean isEmpty() {
return value.length == 0;
}
charAt(int index)方法
截取index位置的字符并返回(返回char数组中的index位置的数据)
public char charAt(int index) {
//这里需要判断索引index是否越界
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
equals()方法
String类中的equals()方法主要是比较两个字符串的内容是否相等
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
//判断字符串的内容是否相同
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
substring()方法
这个方法主要是截取从beginIndex到endIndex的字符
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
//这里返回的是新new的一个String 获取当前value的从beginIndex 截取sublen长度
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
通过这里我们可以看到,subString底层其实使用的构造方法,我们来看一下这个构造方法
public String(char value[], int offset, int count) {
//这里是判断偏移量和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;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
//因为value是一个char 类型的数组 这里使用的是Arrays的copyOfRange方法
//类似System.arraycopy方法
this.value = Arrays.copyOfRange(value, offset, offset+count);
}