87.String类
在Java中,使用String和StringBuffer来表示封装了一系列字符的对象,习惯上,将它们称为“字符串”。
1 String
String 类包含了一个不可改变(immutable)的字符串。一旦一个String实例被创建,包含在这个实例中的内容(“字符串”)不可被更改,直至这个对象被销毁。因此,指向String对象的变量实质上是一个常量,String对象也被称为常量对象。
创建一个String对象有很多种方法,但最常见的方法是将一个字符系列当作参数给String的构造器来创建一个String 实例,如下:
String s = new String(“abcdefgh”);
但也可以使用一种更常见的方式来定义一个String对象:
String s = “abcdefgh”;
在这条语句中,Java将为字符串“abcdefgh”隐式地创建一个String对象。
如果想建立一个空字符串,也可以相应的采用两种方式:
String s = new String();
或
String s = “”;
请注意空字符串(””)和null的区别。空字符串表示的是这个String对象内容为空,而空String对象(null)表示这个String类的变量不指向任何的String实例。
在String类中,定义了很多的方法,通过这些方法,可以创建新的字符串。下面我们会对一些常用的方法做一些简单的介绍。
String的“不可更改”含义
在前面我们说过,String类是不可更改的(immutable),那么什么是不可更改呢?
在String类中,不可更改的意思是,只要生成了一个String实例,那么,它里面的内容是不能被更改的。虽然String有很多的方法,但是没有一个方法可以用来改变它的内容。
我们来看一个例子。
public class ImmutableString{
public static void main(String[] args){
String a = "abcde";
System.out.println("a = "+a+" //1"); //1
String b = a.toUpperCase();
System.out.println("a = "+a+" //2"); //2
System.out.println("b = "+b+" //3"); //3
}
}
在这个类ImmutableString的方法main()中,首先定义了一个String对象a,它的内容为“abcde”,然后在//1处将它打印出来,毫无疑问,此时将打印出“abcde”,然后,调用String类的一个方法toUpperCase(),,它将a中的字符串内容全部变为大写后返回给另一个String变量b,随后,再在//2处将a打印出来,在//3处将b打印出来。在//2处将打印什么值呢?如果toUpperCase()修改的是实例a中的内容,那么,此处应该打印出全部大写的“ABCDE”,而在//3处,毫无疑问,应该是全部大写的“ABCDE”,到底结果如何呢?
编译并运行这个程序,将得到如下的输出:
a = abcde //1
a = abcde //2
b = ABCDE //3
这就说明了,toUpperCase()方法并没有改变a中的内容,而只是将a中的内容全部转化成大写后新生成了一个String对象给变量b。
创建String细节
我们在前面说过,通过下面的方式之一,就可以创建一个String对象:
String s1 = new String(“abc”);
String s2 = “abcde”
那么,这两种创建字符串的方式有没有区别呢?
我们来看一个例子。
public class StringEqual{
public static void main(String[] args){
String s1 = new String("Test"); //1
String s2 = new String("Test"); //2
String s3 = "Test"; //3
String s4 = "Test"; //4
System.out.println("s1 == s2? Answer:"+(s1 == s2)); //5
System.out.println("s1 equals s2? Answer:"+s1.equals(s2)); //6
System.out.println("s3 == s4? Answer:"+(s3 == s4)); //7
System.out.println("s3 equals s4? Answer:"+s3.equals(s4)); //8
}
}
在这个例子中,通过new操作符来创建了两个String对象s1和s2,通过直接指定字符串值的方法创建了s3和s4两个String对象,然后,我们来看一下它们是否相等。在这里我们使用了“==”和“equals()”方法两种方式来比较:“==”比较的是两个变量是否指向同一个对象引用,而“equals()”方法比较的是对象内容是否相等。
编译并运行这个程序,将得到如下的输出:
s1 == s2? Answer:false
s1 equals s2? Answer:true
s3 == s4? Answer:true
s3 equals s4? Answer:true
对于s1和s2的比较结果,完全在我们的意料之中:s1和s2指向的是两个不同的对象,而s1和s2的内容是相等的。但对于s3和s4,就让我们疑惑了,为什么s3==s4的比较居然相同?它们是指向同一个对象么?
答案是肯定的:如果通过直接指定字符串值的方法来创建String对象,它将保存在内存的一个“字符串池”中,而如果“池”中已经存在同样内容的字符串,则将不生成新的字符串,而是直接使用这个已经存在的字符串对象。所以,在行//4中,只是将s4这个变量指向已经存在的对象,也就是s3所指向的对象。因此,在这个时候s3和s4指向的是同一个对象,表达式“s3 == s4”也就等于true了。
对于使用new操作符来创建String对象,无论内存中是否已经存在相同内容的对象,它都会在内存中新建一个新的对象。
从上面的分析我们可以得到一个结论:除非必须,不要使用new操作符来创建字符串对象。在进行字符串内容值的比较的时候,应该使用equals()方法而应该避免使用“==”运算符,除非你能确定所有的String对象是用直接赋值而非用new操作符创建。
除了字符串变量可以使用equals()方法外,字符串常量也可以使用它来和其他的字符串数据比较:
"Test".equals(s1);
上面的表达式是合法的。
在使用equals()进行字符串比较的时候,它是区分大小写的。除了equals()方法外,Java String还提供另外一个不区分大小写的比较方法:equalsIgnoreCase(),除了它不区分大小写以外,用法和equals()一样。
String常用的方法
String提供了很多的方法,下面列出了比较常用的方法:
public String concat(String str)---将str的内容连接到当前字符串的尾部,组成新的String对象并返回。其中str的值不能为null,否则运行出错。此外,如果str的长度为0,则不创建新对象,而直接返回当前对象的句柄。
public String replace(char oldChar, char newChar)---将当前字符串对象中出现的所有oldChar替换为newChar,组成新的String对象并返回。如果oldChar一次也未出现过,则不创建新对象,而直接返回当前对象的句柄。
public String substring(int beginIndex)---从当前字符串中截取子串,范围从beginIndex开始(包括索引为beginIndex的字符)直到结尾,组成新的String对象并返回。注意在字符串中的索引是从0开始的。如果beginIndex=0,则不创建新对象,而直接返回当前对象的句柄。
public String substring(int beginIndex, int endIndex)---与前者类似,截取范围从beginIndex开始直到endIndex。
public String toLowerCase()---将当前字符串中所有字符转换为小写形式,组成新的String对象并返回。如果当前字符串中不含非小写形式的字符,则不创建新对象,而直接返回当前对象的句柄。
public String toUpperCase()---与toLowerCase()相反,将当前字符串中所有的字符转换成大写形式,组成新的String对象返回。
public String trim()---删除当前字符串前后的空格符,组成新的String对象并返回。如果当前字符串中不含前后空格符,则不创建新对象,而直接返回当前对象的句柄。
public boolean startsWith(String prefix)---如果prefix是当前字符串的前缀,则返回true,否则返回false。
public boolean startsWith(String prefix, int toffset)---判断prefix是否是当前字符串从下标toffset开始的子串。
public boolean endsWith(String suffix)---如果prefix是当前字符串的后缀,则返回true,否则返回false。
public int indexOf(int ch)---返回字符(char)ch在当前字符串中第一次出现的位置。如未找到则返回-1。
public int indexOf(int ch, int fromIndex)---返回(char)ch在当前字符串中位置fromIndex以后第一次出现的位置。
public int indexOf(String str)---返回子串str在当前字符串中第一次出现的位置。如未找到则返回-1。
public int indexOf(String str, int fromIndex)---返回子串str在当前字符串中位置fromIndex以后第一次出现的位置。
public int lastIndexOf(String str)/ lastIndexOf(String str,int fromIndex) / lastIndexOf(int ch,int lastIndex) / lastIndexOf(int ch) ---与indexOf方法类似,返回特定字符或子串在当前字符串中(或指定位置以后)最后一次出现的位置。
public boolean equals(Object anObject)---判断当前String对象与参数anObject指定的对象是否等价。只有当anObject也是String类型,且其内容与当前对象相同时返回true,否则返回false。
public boolean equalsIgnoreCase(String anotherString)---与equals()方法类似,但忽略大小写的差异。
public char charAt(int?index)---返回字符串中下标为index处的字符。其中index的取值范围是0~length -1。
public int length()---返回字符串的长度,即字符串中字符的个数。
2 StringBuffer
和String相反,StringBuffer表示一个内容可变的(mutable)字符系列。通过StringBuffer的append()、insert()、reverse()、setCharAt()、setLength()等方法,可以对这个字符串中的内容进行修改。
常用的方法
public StringBuffer append(…)---将参数转换为字符串类型,再追加到当前字符串缓冲区的尾部。此方法针对各种数据类型的参数进行了多次重载,如int、char等简单类型,以及String以及Object等引用类型。
public StringBuffer insert(int offset, …)---将参数转换为字符串类型,再插入到当前缓冲区的指定位置。
public StringBuffer reverse()---将当前缓冲区中的所有字符前后互换,生成一个新的StringBuffer对象。
public void setCharAt(int index, char ch)---将缓冲区中指定位置的字符替换为参数所指定的字符ch
public int length()---返回当前缓冲区中包含的字符数
public int capacity()---返回当前缓冲区的容量大小
提示:
StringBuffer类是线程安全的。在JDK5.0中,新增了一个和StringBuffer对应的StringBuilder类,这个类和StringBuffer具有相同的方法,因此,对StrngBuffer的讨论也基本适用于StringBuilder类。但是,StringBuilder和StringBuffer不同的地方在于,它是非线程安全的类,但它的优势在于,因为少了很多同步操作,在效率上会高于StringBuffer类。因此如果不涉及多线程操作,可以考虑使用StringBuilder来提高方法的执行效率。
3 用于连接两个String的“+”和StringBuffer的append()
在第四章4.1.8节,我们曾经介绍过,在Java中,为了运算方便,对运算符“+”进行了重载,使得它可以用于连接两个String字符串。例如:
String s1 = "Hello";
String s2 = "World";
String s3 = s1 + s2;
这个时候,s3的内容为“HelloWorld”。甚至,我们还可以将上面的代码写成如下的样子:
String s1 = "Hello";
String s2 = "World";
s1 = s1 + s2; //也可写成s1 += s2;
此时,s1的内容也为“HelloWorld”,那么,既然String是不可变的,为什么在这里它的内容发生改变了呢?这是因为,原来s1所指向的那个“Hello”的对象引用已经被“抛弃”了,而s1重新指向了一个新的字符串对象引用“HelloWorld”。
而在StringBuffer类中,可以通过它的一个方法append()来实现类似的功能:
StringBuffer sb1 = new StringBuffer("Hello");
StringBuffer sb2 = new StringBuffer("World");
sb1.append(sb2);
此时,sb1中的内容已经变成了“HelloWorld”了。如果需要得到String类型的字符串数据,使用StringBuffer的toString()方法就可以了:
sb1.toString();
下面是完整的代码:
public class TestPlus{
public static void main(String[] args){
String s1 = "Hello";
String s2 = "World";
s1 += s2;
StringBuffer sb1 = new StringBuffer("Hello");
StringBuffer sb2 = new StringBuffer("World");
sb1.append(sb2);
sb1.toString();
}
}
既然这两种方法都可以用来进行字符串的连接,那么,用哪种方式比较好呢?
从效率上来说,String的“+”操作劣于StringBuffer的append()方法,这是因为在进行“+”操作时,实际上还是需要将String转换成StringBuffer再使用append()方法进行连接(在JDK5中通常是转换成StringBuilder),最后使用StringBuffer的toString()来转回String。如果从编程习惯上考虑,“+”要优于append(),比如,我们看一下如下的代码:
String s1,s2,s3,s4,r1,r2;
s1 = "This";
s2 = " is";
s3 = " another";
s4 = " day!";
r1 = s1+s2+s3+s4; //1
StringBuffer sb1 = new StringBuffer("");
r2 = sb1.append(s1).append(s2).append(s3).append(s4).toString(); //2
//1和//2处实现的功能是一样的,但显然,//1处的代码清晰易读,而//2处的代码就不那么容易看懂了。
上一篇: 82.处理异常