String
String
对象是不可变的。String
类中每一个看起来会修改 String
值的方法,实际上都是创建了一个全新的 String
对象,以包含修改后的字符串内容。而最初的 String
对象则丝毫未动。
String
真正不可变原因:
- 保存字符串的数组被
final
修饰且为私有的,并且String
类没有提供/暴露修改这个字符串的方法。 String
类被final
修饰导致其不能被继承,进而避免了子类破坏String
不可变。
// strings/Immutable.java
public class Immutable {
public static String upcase(String s) {
return s.toUpperCase();
}
public static void main(String[] args) {
String q = "howdy";
System.out.println(q); // howdy
String qq = upcase(q);
System.out.println(qq); // HOWDY
System.out.println(q); // howdy
}
}
/* Output:
howdy
HOWDY
howdy
*/
当把 q
传递给 upcase()
方法时,实际传递的是引用的一个拷贝。其实,每当把 String 对象作为方法的参数时,都会复制一份引用,而该引用所指向的对象其实一直待在单一的物理位置上,从未动过。
回到 upcase()
的定义,传入其中的引用有了名字 s
,只有 upcase()
运行的时候,局部引用 s
才存在。一旦 upcase()
运行结束,s
就消失了。当然了,upcase()
的返回值,其实是最终结果的引用。这足以说明,upcase()
返回的引用已经指向了一个新的对象,而 q
仍然在原来的位置。
StringBuilder与StringBuffer
StringBuilder
与 StringBuffer
都继承自 AbstractStringBuilder
类,在 AbstractStringBuilder
中也是使用字符数组保存字符串,不过没有使用 final
和 private
关键字修饰, AbstractStringBuilder
类还提供了很多修改字符串的方法比如 append
方法。
StringBuilder
提供了丰富而全面的方法,包括 insert()
、replace()
、substring()
,甚至还有reverse()
,但是最常用的还是 append()
和 toString()
。
StringBuilder
是 Java SE5 引入的,在这之前用的是 StringBuffer
。后者是线程安全的(参见并发编程),因此开销也会大些。使用 StringBuilder
进行字符串操作更快一点。
+与StringBuilder
Java 语言本身并不支持运算符重载,“+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的运算符。
// strings/Concatenation.java
public class Concatenation {
public static void main(String[] args) {
String mango = "mango";
String s = "abc" + mango + "def" + 47;
System.out.println(s);
}
}
/* Output:
abcmangodef47
*/
在这里,编译器创建了一个 StringBuilder
对象,用于构建最终的 String
,并对每个字符串调用了一次 append()
方法,最后调用 toString()
生成结果。
在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder
以复用,会导致创建过多的 StringBuilder
对象。
对于三者使用的总结:
- 操作少量的数据: 适用
String
- 单线程操作字符串缓冲区下操作大量数据: 适用
StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用
StringBuffer
字符串操作
以下是 String
对象具备的一些基本方法。重载的方法归纳在同一行中:
方法 | 参数,重载版本 | 作用 |
---|---|---|
构造方法 | 默认版本,String ,StringBuilder ,StringBuffer ,char 数组,byte 数组 | 创建String 对象 |
length() | String 中字符的个数 | |
charAt() | int 索引 | 获取String 中索引位置上的char |
getChars() ,getBytes() | 待复制部分的开始和结束索引,复制的目标数组,目标数组的开始索引 | 复制char 或byte 到一个目标数组中 |
toCharArray() | 生成一个char[] ,包含String 中的所有字符 | |
equals() ,equalsIgnoreCase() | 与之进行比较的String | 比较两个String 的内容是否相同。如果相同,结果为true |
compareTo() ,compareToIgnoreCase() | 与之进行比较的String | 按词典顺序比较String 的内容,比较结果为负数、零或正数。注意,大小写不等价 |
contains() | 要搜索的CharSequence | 如果该String 对象包含参数的内容,则返回true |
contentEquals() | 与之进行比较的CharSequence 或StringBuffer | 如果该String 对象与参数的内容完全一致,则返回true |
isEmpty() | 返回boolean 结果,以表明String 对象的长度是否为0 | |
regionMatches() | 该String 的索引偏移量,另一个String 及其索引偏移量,要比较的长度。重载版本增加了“忽略大小写”功能 | 返回boolean 结果,以表明所比较区域是否相等 |
startsWith() | 可能的起始String 。重载版本在参数中增加了偏移量 | 返回boolean 结果,以表明该String 是否以传入参数开始 |
endsWith() | 该String 可能的后缀String | 返回boolean 结果,以表明此参数是否是该字符串的后缀 |
indexOf() ,lastIndexOf() | 重载版本包括:char ,char 与起始索引,String ,String 与起始索引 | 如果该String 并不包含此参数,就返回-1;否则返回此参数在String 中的起始索引。lastIndexOf ()是从后往前搜索 |
matches() | 一个正则表达式 | 返回boolean 结果,以表明该String 和给出的正则表达式是否匹配 |
split() | 一个正则表达式。可选参数为需要拆分的最大数量 | 按照正则表达式拆分String ,返回一个结果数组 |
join() (Java8引入的) | 分隔符,待拼字符序列。用分隔符将字符序列拼接成一个新的String | 用分隔符拼接字符片段,产生一个新的String |
substring() (即subSequence() ) | 重载版本:起始索引;起始索引+终止索引 | 返回一个新的String 对象,以包含参数指定的子串 |
concat() | 要连接的String | 返回一个新的String 对象,内容为原始String 连接上参数String |
replace() | 要替换的字符,用来进行替换的新字符。也可以用一个CharSequence 替换另一个CharSequence | 返回替换字符后的新String 对象。如果没有替换发生,则返回原始的String 对象 |
replaceFirst() | 要替换的正则表达式,用来进行替换的String | 返回替换首个目标字符串后的String 对象 |
replaceAll() | 要替换的正则表达式,用来进行替换的String | 返回替换所有目标字符串后的String 对象 |
toLowerCase() ,toUpperCase() | 将字符的大小写改变后,返回一个新的String 对象。如果没有任何改变,则返回原始的String 对象 | |
trim() | 将String 两端的空白符删除后,返回一个新的String 对象。如果没有任何改变,则返回原始的String 对象 | |
valueOf() (static ) | 重载版本:Object ;char[] ;char[] ,偏移量,与字符个数;boolean ;char ;int ;long ;float ;double | 返回一个表示参数内容的String |
intern() | 为每个唯一的字符序列生成一个且仅生成一个String 引用 | |
format() | 要格式化的字符串,要替换到格式化字符串的参数 | 返回格式化结果String |
参考资料
深入探究可参考如下内容