前面几个小节主要讲解了String类的特点以及使用方法。实际上,Java语言中并不只有String类能表示字符串,StringBuffer和StringBuilder也可以表示字符串,它们共同构成了字符串家族。String、StringBuffer和StringBuilder这三个类都实现了CharSequence接口。CharSequence是两个单词的组合,其意义为“字符序列”。CharSequence接口中定义了很多操作字符串的方法,有些是抽象方法,有些是已经实现了的方法,其中String类在CharSequence接口的基础上扩展出的方法最多,因此String类与其他两个类相比功能更强大,并且程序员在大多数情况下也都会选择用String类来表示字符串。
为什么已经有了强大String类,还要定义出StringBuffer和StringBuilder这两个类来表示字符串呢?就是因为String是一个不可变类,这种不可变性导致了每次对字符串进行修改都会产生一个新的String对象。有时候,程序员需要连续的对字符串进行修改,这样就会导致新产生的String对象消耗了大量的内存空间。为了解决这个问题,Java语言又定义出了StringBuffer和StringBuilder这两个类来解决这个问题。StringBuffer和StringBuilder是可变类,程序员可以直接对它们的值进行修改,修改值之后不会产生任何新的对象。
String、StringBuffer和StringBuilder这三个类中都定义了一个名叫value的byte型的数组,这个数组用于存储字符序列。为什么这三个类存储字符序列的原理相同,但String是不可变类,而StringBuffer和StringBuilder却是可变类呢?这是因为每次对字符串进行修改时,String类不会改变原有value数组的内容,而是会新创建一个数组来存储字符序列,并且以这个数组为基础创建一个新的String对象,而StringBuffer和StringBuilder则不同,每次修改字符串时,都直接在原有的value数组上进行操作,不会创建出新的对象。下面的【例09_16】展示了三种不同的字符串类在修改值之后是否产生新的对象。
【例09_16 在字符串末尾添加数据】
Exam09_16.java
【例09_16】中分别用String类的concat()方法以及StringBuffer和StringBuilder的append()方法对字符串进行了拼接。之后用拼接产生的字符串和原字符串进行比较。【例09_16】的运行结果如图9-11所示:
图9-11【例09_16】运行结果
从图9-11可以看出:String类在对字符串进行拼接之后会产生新的对象,而StringBuffer和StringBuilder在对字符串进行拼接之后没有产生新对象,这说明拼接操作是在原对象上直接进行的。
Java语言中定义StringBuffer和StringBuilder这两类就是为了在修改字符串时不产生新的对象,因此这两个类中定义了许多用来修改字符串的方法。下面的表9-1就列举了这两个类中用于修改字符串的方法:
表9-1 修改字符串的方法
方法名 | 作用 |
append() | 在字符串末尾添加数据 |
delete() | 去除字符串中的一部分 |
deleteCharAt() | 删除指定位置的字符 |
insert() | 在字符串指定位置添加数据 |
replace() | 替换指定位置的子字符串 |
reverse() | 反转字符串 |
setCharAt() | 替换指定位置的字符 |
表9-1中的append()方法和insert()方法其实有很多重载版本,这使得程序员可以把任意类型的数据添加到字符串中,当然,这些被添加到字符串中的数据最终也会被转换成字符串。下面的【例09_17】演示了使用这些方法对字符串进行修改的效果。
【例09_17 修改字符串】
Exam09_17.java
修改字符串的方法一般都有返回值,这个返回值就是StringBuffer对象自身被修改过的结果。但setCharAt()方法却没有返回值,所以不能直接打印方法的执行结果。【例09_17】中列举的append()方法和insert()方法有很多重载版本,这使得程序员可以把任意类型的数据添加到字符串中。【例09_17】的运行结果如图9-12所示:
图9-12 【例09_17】运行结果
各位读者可以根据表9-1以及程序的运行结果仔细体会修改字符串方法的功能和作用。
StringBuffer和StringBuilder都是用来完成字符串修改的类,那么它们之间有什么区别呢?如果查看源代码,你会发现StringBuffer和StringBuilder这两个类中所定义的方法是完全相同的。而它们之间的区别仅仅在于StringBuffer可以保证线程同步,而StringBuilder则不能。“线程同步”是一个目前还没讲到的概念,这里举例解释一下:在生活当中有很多东西是多人共用的,比如说一个家庭里有一台电脑,任何家庭成员都可以使用它。所谓保证同步就好比是:一个人在使用电脑期间,另一个人不能同时使用,直到使用电脑的人彻底用完了之后其他人才能使用。而StringBuilder能够保证线程同步的意思是:一个线程在操作某个StringBuffer对象期间其他线程不能对这个对象有任何操作。
线程同步虽然能够保证数据安全,但使得对象的利用率降低,并且为了实现同步还会增加系统开销。很多情况下实际上是不需要保证线程同步的,比如单机应用模式下的字符串处理往往不需要保证同步,这种情况下就可以使用StringBuilder来处理字符串。因此:选择使用StringBuffer和StringBuilder的关键就在于是否需要保证线程同步。
前文曾经讲过:String类对象可以通过加号实现拼接操作,实际上,用加号拼接String类对象的语句在编译之后会形成两种结果:如果拼接结果编译阶段就能确定值,那么就直接把拼接表达式替换为拼接结果,例如下面的语句:
这条语句会被编译为:
如果拼接结果不能在编译阶段确定值,那么拼接操作就会被编译为用StringBuilder类的append()方法连接字符串,例如:
以上代码中,str有s1和s2通过加号拼接而成,由于s1和s2之前都没有添加final关键字,所以拼接结果无法在编译阶段确定,这种情况下以上代码会被编译为:
通过编译结果看出:用加号拼接String类对象的操作实际上是由StringBuilder类的append()方法实现的,并且在执行完append()方法之后会调用toString()方法把StringBuilder类对象在转换为String类对象。正因为append()方法有各种重载版本,所以String类对象用加号能与各种类型的数据进行拼接。
本文字版教程还配有更详细的视频讲解,小伙伴们可以点击这里观看。