简介
String 是 由 final 声明的一个不可被继承的类,其值在初始化后便不可更改。
属性
private final char value[];
private int hash; // Default to 0
value属性就是String类的核心,一个String字符串本质就是一个char类型的数组,另外hash属性则是用于比较的优化,后面的注释是指一个空字符串默认的hash值是0.
public static void main(String[] args) {
System.out.println("空字符串哈希值:"+"".hashCode());
}
输出:
hashCode 函数分析:
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;
}
可以看到,当value长度大于0时,hash值将重新计算,否则其值未被初始化,默认为0。
构造函数简介
String 提供了许多构造函数用于构造String对象,这里简要介绍几种:
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
可以看到,这种构造方法直接将当前字符串的value指向源字符串的value,hash值赋值为源字符串的hash值,但这并不会导致一个String对象的修改影响到源字符串,因为String被设计为不可变的,当字符串发生改变,他会创建一个新的字符串,并指向新的字符串。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
String类可以通过传入一个char数组构造一个String对象,这里调用了Arrays.copyOf 方法,复制了一个相同的数组赋值给value,并不是直接将value指向这个数组,这样可以防止原数组被修改导致String对象的值被修改。
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
String类也可以通过传入字节数组构造String对象,这里它调用了另一个构造函数:
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
其中checkBounds是对传入参数的校验:
private static void checkBounds(byte[] bytes, int offset, int length) {
if (length < 0)
throw new StringIndexOutOfBoundsException(length);
if (offset < 0)
throw new StringIndexOutOfBoundsException(offset);
if (offset > bytes.length - length)
throw new StringIndexOutOfBoundsException(offset + length);
}
真正对value的复制则是调用了StringCoding.decode方法:
static char[] decode(byte[] ba, int off, int len) {
String csn = Charset.defaultCharset().name();
try {
// use charset name decode() variant which provides caching.
return decode(csn, ba, off, len);
} catch (UnsupportedEncodingException x) {
warnUnsupportedCharset(csn);
}
try {
return decode("ISO-8859-1", ba, off, len);
} catch (UnsupportedEncodingException x) {
// If this code is hit during VM initialization, MessageUtils is
// the only way we will be able to get any kind of error message.
MessageUtils.err("ISO-8859-1 charset not available: "
+ x.toString());
// If we can not find ISO-8859-1 (a required encoding) then things
// are seriously wrong with the installation.
System.exit(1);
return null;
}
}
这个函数会获得当前系统的字符编码,通过它来解析字节数组,如果解析失败则使用”ISO-8859-1”来解码数组,再失败则程序退出,返回null。String类字节数组相关的构造函数也提供了指定编码方式的构造函数,过程类似,只是将默认编码方式改为指定编码方式。
常用方法简介
length()、isEmpty()、charAt():
public int length() {
return value.length;
}
public boolean isEmpty() {
return value.length == 0;
}
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
这三个方法都是直接通过操作value属性实现,还有类似的getChars方法含有多个重载方法,通过复制value数组指定区间的值实现。
equals:
public boolean equals(Object anObject) {
//比较的对象与当前字符串是同一对象,返回true
if (this == anObject) {
return true;
}
//对象不是String类型返回false
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
//长度不同,返回false
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;
}
equals函数并未直接开始比较字符串的每个字符,而是处理了几种特殊情况后开始比较,提高了比较的效率。
还有许多函数不一一列举。
String类’+’号的使用:
代码执行结果如下:
public static void main(String[] args) {
String a = "Hello";
String b = a;
b += " World";
System.out.println(a);//Hello
System.out.println(b);//Hello World
}
javap 反编译后的结果:
反编译后虽然不易理解,但几行注释依然可以看出一些端倪,
图中第一个红箭头可以看到它创建了一个StringBuilder对象,往下通过String.valueOf获取字符串值为StringBuilder对象做了初始化,第二个红箭头处调用了StringBuilder.append
方法,第三个红箭头则调用了toString方法,这里就是对”+”号的处理,即b是指向了由StringBuilder.toString生成的字符串,StringBuilder.toString源码如下:
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
该函数返回了一个新的String对象,因此字符串的改变总会生成一个新的对象,并不会影响原字符串。String类还有不少操作字符串的函数,其实都是复制value数组后生成一个新的字符串,同样不影响原字符串。