欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

87.String类

程序员文章站 2022-04-28 21:21:34
...

在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处的代码就不那么容易看懂了。