10. 面向对象思考
10.1 类的抽象和封装
类的抽象是指将类的实现和类的使用分离开,实现的细节被封装并且对用户隐藏,这被称为类的封装。
前面已经学习了方法的抽象以及如何在逐步求精中使用它。Java 提供了多层次的抽象。类抽象(class abstraction) 是将类的实现和使用分离。类的创建者描述类的功能,让使用者明白如何才能使用类。从类外可以访问的方法和数据域的集合以及预期这些成员如何行为的描述,合称为类的合约(class’s contract)。如下图所示,类的使用者不需要知道类是如何实现的。实现的细节经过封装,对用户隐藏起来,这称为类的封装(class encapsulation)。例如:可以创建一个 Circle 对象,并且可以在不知道面积是如何计算出来的情况下,求出这个圆的面积。由于这个原因,类也称为抽象数据类型(Abstract Data Type, ADT)。
类的抽象和封装是一个问题的两个方面。现实生活中的许多例子都可以说明类抽象的概念。例如:考虑建立一个计算机系统。个人计算机有很多组件~~CPU、内存、磁盘、 主板和风扇等。每个组件都可以看作是一个有属性和方法的对象。要使各个组件一起工作,只需要知道每个组件是怎么用的以及是如何与其他组件进行交互的,而无须了解这些组件内部是如何工作的。内部功能的实现被封装起来,对你是隐藏的。所以,你可以组装一台计算机,而不需要了解每个组件的功能是如何实现的。
对计算机系统的模拟准确地反映了面向对象方法。每个组件可以看成组件类的对象。例如,你可能已经建立了一个类,模拟用在计算机上的各种类型的风扇,它具有风扇尺寸和速度等属性,还有像开始和停止这样的方法。一个具体的风扇就是该类具有特定属性值的实例。
10.2 面向对象的思考
面向过程的范式重点在于设计方法。面向对象的范式将数据和方法耦合在一起构成对象。使用面向对象范式的软件设计重点在对象以及对对象的操作上。
一个程序的代码在 main 方法中,就不能在其他程序中重用。为使之具备可重用性,定义一个静态方法计算身体质量指数,如下所示:
public static double getBMI(double weight, double height)
这个方法对于计算给定体重和身高的身体质量指数是很有用的。但是,它是有局限性的。假设需要将体重和身髙同一个人的名字与出生日期相关联,虽然可以分别声明几个变量来存储这些值,但是这些值不是紧密耦合在一起的。将它们耦合在一起的理想方法就是创建一个包含它们的对象。因为这些值都被绑定到单独的对象上,所以它们应该存储在实例数据域中。可以定义一个名为 BMI的类,如下图所示:
假设 BMI 类是可用的。下列程序清单给出使用这个类的测试程序。显示
UseBMICIass.java
public class UseBMICIass {
public static void main(String[] args){
BMI bmil = new BMI("Kim Yang",18, 145, 70);
System.out.println("The BMI for " + bmil.getName() + " is ")+ bmi1.getBMI() + " " + bmil.getStatus());
BMI bmi2 = new BMI("Susan King", 215, 70);
System.out.println("The BMI for " + bmi2.getName()+ " is " + bmi2.getBMI() + " "bmi2.getStatus());
}
}
显示
The BMI for Kim Yang is 20.81 Normal
The BMI for Susan King is 30.85 0bese
第 3 行为 Kim Yang 创建一个对象 bmi1, 而第 7行为 Susan King 创建一个对象 bmi2。可以使用实例方法 getName( )、getBMI( )和 getStatus( )返回一个 BMI 对象中的 BMI 信息。
10.3 类的关系
为了设计类,需要探究类之间的关系。类中间的关系通常是关联、聚合、组合以及继承。
10.3.1 关联
关联是一种常见的二元关系,描述两个类之间的活例如,学生选取课程是 Student类和 Course 类之间的一种关联,而教师教授课程是 Faculty 类和 Course 类之间的关联。这些关联可以使用 UML 图形标识来表达,如下图所示:
关联由两个类之间的实线表示,可以有一个可选的标签描述关系。上图中,标签是Take 和 Teach。每个关系可以有一个可选的小的黑色三角形表明关系的方向。在该图中,方向表明学生选取课程(而不是相反方向的课程选取学生)。
关系中涉及的每个类可以有一个角色名称,描述在该关系中担当的角色。上图中,Teacher 是 Faculty 的角色名。关联中涉及的每个类可以给定一个多重性(multiplicity), 放置在类的边上用于给定 UML 图中关系所涉及的类的对象数。多重性可以是一个数字或者一个区间,决定在关系中涉及类的多少个对象。字符 * 意味着无数多个对象,而 m ..n 表示对象数处于 m 和 n 之间,并且包括 m 和 n。上图中,每个学生可以选取任意数量的课程数,每门课程可以有至少 5 个最多 6 个学生。每门课程只由一位教师教授,并且每位教师每学期可以教授 0 到 3门课程。
在 Java 代码中,可以通过使用数据域以及方法来实现关联。例如,上图中的关系可以使用下图中的类来实现。关系 “一个学生选取一门课程” 使用 Student类中的 addCourse 方法和 Course 类中的 addStudent 方法实现。关系 “一位教师教授一门课程” 使用 Faculty 类中的 addCourse 方法和 Course 类中的 setFaculty 方法实现。Student 类可以使用一个列表来存储学生选取的课程,Faculty 类可以使用一个列表来存储教师教授的课程,Course 类可以使用一个列表来存储课程中登记的学生以及一个数据域来存储教授该课程的教师。
注意:实现类之间的关系可以有很多种可能的方法。例如,Course 类中的学生和教师信息可以省略,因为它们已经在 Student 和 Faculty 类中了。同样的,如果不需要知道一个学生选取的课程或者教师教授的课程,Student 或者 Faculty 类中的数据域 courseList 和 addCourse 方法也可以省略。
10.3.2 聚集和组合
聚集是关联的一种特殊形式,代表了两个对象之间的归属关系。聚集建模 has -a 关系。所有者对象称为聚集对象,它的类称为聚集类。而从属对象称为被聚集对象,它的类称为被聚集类。
— 个对象可以被多个其他的聚集对象所拥有。如果一个对象只归属于一个聚集对象,那么它和聚集对象之间的关系就称为组合(composition)。例如:“一个学生有一个名字” 就是学生类 Student 与名字类 Name 之间的一个组合关系,而 “一个学生有一个地址” 是学生类 Student 与地址类 Address 之间的一个聚集关系,因为一个地址可以被几个学生所共享。在 UML 中,附加在聚集类(例如:Student ) 上的实心菱形表示它和被聚集类(例如:Name ) 之间具有组合关系;而附加在聚集类(例如:Student)上的空心菱形表示它与被聚集类(例如:Address)之间具有聚集关系,如下图所示:
在上图中,每个学生只能有一个地址,而每个地址最多可以被 3 个学生共享。每个学生都有一个名字,而每个学生的名字都是唯一的。
聚集关系通常被表示为聚集类中的一个数据域。例如:上图中的关系可以使用下图中的类来实现。关系 “一个学生拥有一个名字” 以及 “一个学生有一个地址” 在 Student 类中的数据域 name 和 address 中实现。
聚集可以存在于同一类的多个对象之间。例如:一个人可能有一个管理者,如下图所示:
在关系 “一个人有一个管理者” 中,管理者可以如下表示为 Person 类的一个数据域:
public class Person {
// The type for the data is the class itself
private Person supervisor;
...
}
如果一个人可以有几个管理者,如下图 a 所示,可以用一个数组存储管理者,如下图b所示。
注意:由于聚集和组合关系都以同样的方式用类来表示,我们不区分它们,将两者都称为组合。
10.4 将基本数据类型值作为对象处理
基本数据类型值不是一个对象,但是可以使用 JavaAPl 中的包装类来包装成一个对象。
出于对性能的考虑,在 Java 中基本数据类型不作为对象使用。因为处理对象需要额外的系统开销,所以,如果将基本数据类型当作对象,就会给语言性能带来负面影响。然而,Java 中的许多方法需要将对象作为参数。Java 提供了一个方便的办法,即将基本数据类型并入对象或包装成对象(例如,将 int 包装成 Integer 类,将 double 包装成 Double 类,将 char 包装成 Character 类)。通过使用包装类,可以将基本数据类型值作为对象处理。Java 为基本数据类型提供了 Boolean、Character、Double、Float、 Byte、Short、Integer 和 Long 等包装类。这些包装类都打包在 java.lang 包里。Boolean 类包装了布尔值 true 或者 false。本节使用 Integer 和 Double 类为例介绍数值包装类。
注意:大多数基本类型的包装类的名称与对应的基本数据类型名称一样,第一个字母要大写。Integer 和 Character 例外。数值包装类相互之间都非常相似。每个都包含了doubleValue( ) 、floatValue( ) 、intValue( )、longValue( )、shortValue( ) 和 byteValue( )方法。这些方法将对象 “转换” 为基本类型值。Integer 类和 Double 类的主要特征如下图所示:
既可以用基本数据类型值也可以用表示数值的字符串来构造包装类。例如,new Double(5.0)、new Double("5 .0")、new Integer(5)和 new Integer("5")。
包装类没有无参构造方法。所有包装类的实例都是不可变的,这意味着一旦创建对象后,它们的内部值就不能再改变。
每一个数值包装类都有常量 MAX_VALUE 和 MIN_VALUE。MAX_VALUE 表示对应的基本数据类型的最大值。 对于 Byte 、 Short、Integer 和 Long 而言,MIN_VALUE 表示对应的基本类型 byte、short、int 和 long 的最小值。对 Float 和 Double 类而言,MIN_VALUE 表示 float 型和 double 型的最小正值。下面的语句显示最大整数( 2 147 483 647 )、最小正浮点数( 1.4E - 45 ),以及双精度浮点数的最大值( 1.79769313486231570e+308d ):
System.out.println("The maximum integer is " + Integer.MAX_VALUE);
System.out.println("The minimum positive float is " + Float.MIN_VALUE);
System.out.println("The maximum double-precision floating-point number is " + Double.MAX.VALUE);
每个数值包装类都会包含方法 doubleValue( ) 、floatValue( )、intValue( )、longValue( ) 和 shortValue( )。这些方法返回包装对象的 double、float、int、long 或 short 值。例如:
new Doub1e(12.4).intValue() returns 12;
new Integer(12).doubleValue() returns 12.0;
回顾下 String 类中包含 compareTo 方法用于比较两个字符串。数值包装类中包含 compareTo 方法用于比较两个数值,并且如果该数值大于、等于或者小于另外一个数值时, 分别返回 1、0、-1。例如:
new Doub1e(12.4).compareTo(new Doub1e(12.3)) returns 1;
new Double(12.3).compareTo(new Doub1e(12.3)) returns 0;
new Doub1e(12.3).compareTo(new Double(12.51)) returns -1;
数值包装类有一个有用的静态方法 valueOf( String s)。该方法创建一个新对象,并将它初始化为指定字符串表示的值。例如:
Double doubleObject = Double.valueOf("12.4");
Integer integerObject = Integer.valueOf("12");
我们已经使用过 Integer 类中的 parselnt 方法将一个数值字符串转换为一个 int 值,而且使用过 Double 类中的 parseDouble 方法将一个数值字符串转变为一个 double 值。每个数值包装类都有两个重载的方法,将数值字符串转换为正确的以 10(十进制)或指定值为基数(例如,2 为二进制,8为八进制,16 为十六进制)的数值。
10.5 基本类型和包装类类型之间的自动转换
根据上下文环境,基本数据类型值可以使用包装类自动转换成一个对象,反过来的自动转换也可以。
将基本类型值转换为包装类对象的过程称为装箱( boxing), 相反的转换过程称为开箱 ( unboxing)。Java 允许基本类型和包装类类型之间进行自动转换。如果一个基本类型值出现在需要对象的环境中,编译器会将基本类型值进行自动装箱;如果一个对象出现在需要基本类型值的环境中,编译器会将对象进行自动开箱。这称为自动装箱和自动开箱。例如,可以用自动装箱将图 a 中的语句简化为图 b 中的语句:
考虑下面的例子:
Integer[] intArray = {1, 2, 3};
System,out.println(intArray[0]+ intArray[1]+ intArray[2]);
在第一行中,基本类型值 1、2 和 3 被自动装箱成对象 new Integer(1)、new Integer(2)和 new Integer(3)。第二行中,对象 intArray[0]、intArray[1]和 intArray[2] 被自动转换为 int 值,然后进行相加。
10.6 Biglnteger 和 BigDecimal 类
Biglnteger 类和 BigDecimal 类可以用于表示任意大小和精度的整数或者十进制数。
如果要进行非常大的数的计算或者高精度浮点值的计算,可以使用 java.math 包中的 Biglnteger 类和 BigDecimal 类。它们都是不可变的。long 类型的最大整数值为 long.MAX_ VALUE (即 9223372036854775807)。Biglnteger 的实例可以表示任意大小的整数。可以使用 new Biglnteger(String)和 new BigDecimal(String)来 创 建 Biglnteger 和 BigDecimal 的实例,使用 add、subtract、multiple、divide 和 remainder 方法完成算术运算,使用 compareTo 方法比较两个大数字。例如,下面的代码创建两个 Biglnteger 对象并且将它们进行相乘:
Biglnteger a = new BigInteger("9223372036854775807");
Biglnteger b = new BigInteger("2");
Biglnteger c = a.multiply(b); // 9223372036854775807 * 2
System.out.println(c);
它的输出为 18446744073709551614。
对 BigDecimal 对象的精度没有限制。如果结果不能终止,那么divide 方法会抛出 ArithmeticException 异常。但是,可以使用重载的 divide(BigDecimal d.int scale, int roundingMode)方法来指定尺度和舍入方式来避免这个异常,这里的 scale 是指小数点后最小的整数位数。例如,下面的代码创建两个尺度为 20、舍入方式为 BigDecimal .R0UND_UP的 BigDecimal 对象。
BigDecimal a = new BigDecimal(1.0);
BigDecimal b = new BigDecimal(3):
BigDecimal c = a.divide(b, 20, BigDecimal .ROUND_UP);
System.out.println(c);
输出为 0.33333333333333333334。
注意: 一个整数的阶乘可能会非常大。
10.7 String 类
String 对象是不可改变的。字符串一旦创建,内容不能再改变。
10.7.1 构造字符串
可以用字符串直接量或字符数组创建一个字符串对象。使用如下语法,用字符串直接量创建—个字符串:
String newString = new String(stringLiteral);
参数 StringLiteral 是一个括在双引号内的字符序列。下面的语句为字符串直接量 "Welcome to Java"创建一个 String 对象 message:
String message = new String("Welcome to Java");
Java 将字符串直接量看作 String 对象。所以,下面的语句是合法的:
String message = "Welcome to Java";
还可以用字符数组创建一个字符串。例如,下述语句构造一个字符串 "Good Day":
char[] charArray = {'G','o', 'o' , 'd' , ' ' , 'o' , 'a' ,'y'};
String message = new String(charArray);
注意:String 变量存储的是对 String 对象的引用,String 对象里存储的才是字符串的值。严格地讲,术语 String 变量、String 对象和字符串值是不同的。但在大多教情况下,它们之间的区别是可以忽略的。为简单起见,术语字符串将经常被用于指 String 变量、String 对象和字符串的值。
10.7.2 不可变字符串与限定字符串
String 对象是不可变的,它的内容是不能改变的。下列代码会改变字符串的内容吗?
String s = "Java";
s = "HTML":
答案是不能。第一条语句创建了一个内容为"Java" 的 String 对象,并将其引用赋值给So 第二条语句创建了一个内容为"HTML"的新 String 对象,并将其引用赋值给 s。陚值后第一个 String 对象仍然存在,但是不能再访问它,因为变量 s 现在指向了新的对象,如下图所示:
因为字符串在程序设计中是不可变的,但同时又会频繁地使用,所以 Java 虚拟机为了提高效率并节约内存,对具有相同字符序列的字符串直接量使用同一个实例。这样的实例称为限定的(interned)字符串。例如,下面的语句:
String s1 = "Welcome to ]ava";
String s2 = new String("Weicome to ]ava");
String s3 = "Welcome to ]ava";
System.out.println("sl==s2 is " + (sl==s2));
System.out.println("sl==s3 is " + (sl==s3));
程序结果显示:
s1 = s2 is false
s1 = s3 is true
在上述语句中,由于 s1 和 s3 指向相同的限定字符串 "Welcome to Java", 因此,s1 -s3 为 true。但是,s1 - s2 为 false, 这是因为尽管 s1和 s2 的内容相同,但它们是不同的字符串对象。
10.7.3 字符串的替换和分隔
String 类提供替换和分隔字符串的方法,如下图所示:
一旦创建了字符串,它的内容就不能改变。但是,方法 repalce、replaceFirst 和 replaceAll 会返回一个源自原始字符串的新字符串(并未改变原始字符串!)。方法 replace 有好几个版本,它们实现用新的字符或子串替换字符串中的某个字符或子串。
例如:
"Welcome".replace('e', 'A') 返回一个新的字符串,WAlcomA.
"Welcome".replaceFirst("e","AB") 返回一个新的字符串,WABlcome.
"Welcome".replace("e","AB")返回一个新的字符串,WABlcomAB.
"Welcome”.replace("el", "AB") 返回一个新的字符串,WABcome.
split 方法可以从一个指定分隔符的字符串中提取标识。例如,下面的代码:
String[] tokens = "Java#HTML#Perl".split("#");
for (int i = 0; i < tokens.length; i++)
System.out.print(tokens[i] + " ");
显示
Java HTML Perl
10.7.4 依照模式匹配、替换和分隔
正则表达式(regular expression)(缩写 regex) 是一个字符串,用于描述匹配一个字符串集的模式。可以通过指定某个模式来匹配、替换或分隔一个字符串。这是一种非常有用且功能强大的特性。
从 String 类中的 matches 方法开始。乍一看,matches 方法和 equals 方法非常相似。例如,下面两条语句的值均为 true:
"Java".matches("Java");
"Java".equals("Java");
但是,matches 方法的功能更强大。它不仅可以匹配定长的字符串,还能匹配一套遵从某种模式的字符串。例如,下面语句的结果均为 true:
"Java is fun".matches("Java.*")
"Java is cool".matches("Java.*")
"Java is powerful".matches("Java.*")
在前面语句中的 "Java.*" 是一个正则表达式。它描述的字符串模式是以字符串 Java 开始的,后面紧跟任意 0 个或多个字符。这里,子串 .* 与 0个或多个字符相匹配。
下面语句结果为 true.
'440-02-4534".matches("\\d{3}-\\d{2}-\\d{4}")
这里 \\d 表示单个数字位,\\d{3}表示三个数字位。
方法 replaceAll、replaceFirst 和 split 也可以和正则表达式结合在一起使用。例如,下面的语句用字符串 NNN 替换 "a+b$#c" 中的 $、+ 或者 #,然后返回一个新字符串。
String s = "a+b$#c".replaceAll("[$+#]", "NNN"):
System.out.println(s);
这里的正则表达式 [$+#]表示能够匹配 $、+ 或者 * 的模式。所以,输出是 aNNNbNNNNNNc。
下面的语句将字符串分隔为由标点符号分隔开的字符串数组。
String[] tokens = "Java.C?C#.C++".split("[.,: ?]");
for (int i = 0; i < tokens.length; i++)
System.out.println(tokens[i]);
这里的正则表达式指定的模式是指匹配 .、,、:、 ;或者?。这里的每个字符都是分隔字符串的分隔符。因此,这个字符串就被分割成;Java、C、C# 和 C++, 它们都存储在数组 tokens 中。
10.7.5 字符串与数组之间的转换
字符串不是数组,但是字符串可以转换成数组,反之亦然。为了将字符串转换成一个字符数组,可以使用 toCharArray 方法。例如,下述语句将字符串"Java"转换成一个数组:
char[] chars = "Java".toCharArray();
因此,chars[0]是 'J’, chars[1]是 ’a', chars[2]是 ’v’,chars[3]是 'a’。
还可以使用方法 getChars(int srcBegin.int srcEnd,char[ ]dst,int dstBegin)将下标从 srcBegin 到 srcEnd -1的子串复制到字符数组 dst 中下标从 dstBegin 开始的位置。例如,下面的代码将字符串"CS3720"中下标从 2 到 6-1的子串"3720"复制到字符数组 dst 中下标从 4 开始的位置:
char[] dst = {'J' ,'A' , 'V' , 'A' , '1' , '3' ,'0' , '1' };
"CS3720".getChars(2, 6, dst, 4);
这样,dst 就变成了{'J' ,'A' , 'V' , 'A' , '3' , '7' ,'2' , '0' };
为了将一个字符数组转换成一个字符串,应该使用构造方法 String(Char[ ]) 或者方法 value0f(char[ ])。例如,下面的语句使用 String 构造方法由一个数组构造一个字符串:
String str = new String(new char[]{'J' , 'a' , 'v' , 'a'});
下面的语句使用 valueOf方法由一个数组构造一个字符串:
String str = String.valueOf(new char[]{'J' , 'a' , 'v' , 'a'});
10.7.6 将字符和数值转换成字符串
回顾下,可以使用 Double.parseDouble(str)或者 Integer_ parselnt(str)将一个字符串转换为一个 double 值或者一个 int 值,也可以使用字符串的连接操作符来将字符或者数字转换为字符串。另外一种将数字转换为字符串的方法是使用重载的静态 valueOf 方法。该方法可以用于将字符和数值转换成字符串,如下图所示:
例如,为了将 double 值 5 .44 转换成字符串,可以使用 String.value0f(5 .44)。返回值是由字符'5' 、'.' 、 '4' 和 '4' 构成的字符串。
10.7.7 格式化字符串
String 类包含静态 format 方法,它可以创建一个格式化的字符串。调用该方法的语法是:
String.format(format, item1, item2,..., itemk)
这个方法很像 Printf 方法,只是 format 方法返回一个格式化的字符串,而 printf 方法显示一个格式化的字符串。例如:
String s = String.format("%7.2f%6d%- 4s", 45.556, 14, "AB");
System.out.println(s);
显示
□□45.56□□□□14AB□□
注意
System.out.printf(format, item1, item2,...,itemk);
等价于
System.out.print(String,format(format, item1, item2,...,itemk));
这里,小方形框表示一个空格。
10.8 StringBuilder 和 StringBuffer 类
StringBuilder 和 StringBuffer 类似于 String 类,区别在于 String 类是不可改变的。
— 般来说,只要使用字符串的地方,都可以使用StringBuilder/StringBuffer类。 StringBuilder/StringBuffer类比 String类更灵活。可以给一个 StringBuilder 或 String - Buffer 中添加、插入或追加新的内容,但是 String 对象一旦创建,它的值就确定了。
除了 StringBuffer 中修改缓冲区的方法是同步的,这意味着只有一个任务被允许执行方法之外,StringBuilder 类与 StringBuffer 类是很相似的。如果是多任务并发访问,就使用 StringBuffer, 因为这种情况下需要同步以防止 StringBuffer 崩溃。而如果是单任务访问,使用 StringBuilder 会更有效。StringBuffer 和 StringBuilder 中的构造方法和其他方法几乎是完全一样的。本节介绍 StringBuilder。在本节的所有地方 StringBuilder 都可以替换为 StringBuffer。程序可以不经任何修改进行编译和运行。
StringBuilder 类有 3 个构造方法和 30多个用于管理构建器或修改构建器内字符串的方法。可以使用构造方法创建一个空的构建器或从一个字符串创建一个构建器,如下图所示:
10.8.1 修改 StringBuilder 中的字符串
可以使用下图中列出的方法,在字符串构建器的末尾追加新内容,在字符串构建器的特定位置插人新的内容,还可以删除或替换字符串构建器中的字符。
StringBuilder 类提供了几个重载方法,可以将 boolean、char、char 数组、double、 float、int、long 和 String 类型值追加到字符串构建器。例如,下面的代码将字符串和字符追加到 StringBuilder, 构成新的字符串 "Welcome to Java"。
StringBuilder StringBuilder = new StringBuilder();
stringBuilder = append("Welcome");
stringBui1der.append(' ');
stringBuilder.append("to");
stringBui1der.append(' ');
stringBui1der.append("]ava");
StringBuilder 类也包括几个重载的方法,可以将 boolean、char 、char 数组、double、 float、int,long 和 String 类型值插人到字符串构建器。考虑下面的代码:
StringBuilder.insert(11, "HTML and ");
假设在应用 insert 方 法 之 前,StringBuilder 包含的字符串是 "Welcome to Java"。上面的代码就在 stringBuilder 的第 11个位置(就在 J 之前) 插人 "HTML and"。新的 stringBuilder 就变成 "Welcome to HTML and Java"。
也可以使用两个 delete 方法将字符从构建器中的字符串中删除,使用 reverse 方法倒置字符串,使用 replace 方法替换字符串中的字符,或者使用 setCharAt 方法在字符串中设置一个新字符。
例如,假设在应用下面的每个方法之前,stringBuilder 包含的是 "Welcome to Java"。
stringBui1der.delete(8,11)将构建器变为 Welcome ]ava
stringBuilder.deleteCharAt(8)将构建器变为 Welcome o ]ava
stringBuilder.reverse() 将构建器变为 avaJ ot emocleW
stringBuilder.replace(11,15,"HTML")将构建器变为 Welcome to HTML
stringBuilder.setCharAt(0, 'w')将构建器变为 welcome to Java
除了 setCharAt 方法之外,所有这些进行修改的方法都做两件事:
- 改变字符串构建器的内容。
- 返回字符串构建器的引用。
例如,下面的语句:
StringBuilder strlngBuilderl = stringBui1der.reverse();
将构建器中的字符倒置并把构建器的引用陚值给 stringBuilderl。这样,stringBuilder 和 stringBuilderl都指向同一个 StringBuffer 对象。回顾一下,如果对方法的返回值不感兴趣,所有带返回值类的方法都可以被当作语句调用。在这种情况下,Java 就简单地忽略掉返回值。例如,下面的语句
stringBuilder.reverse();
它的返回值就被忽略了。
提示:如果一个字符串不需要任何改变,则使用 String 类而不使用 StringBuffer 类。 Java 可以完成对 String 类的优化,例如,共享限定字符串等。
10.8.2 toString、capacity、length、setLength 和 charAt 方法
StringBuilder 类提供了许多其他处理字符串构建器和获取它的属性的方法,如下图所示:
capacity( )方法返回字符串构建器当前的容量。容量是指在不增加构建器大小的情况下能够存储的字符数量。
lengthC)方法返回字符串构建器中实际存储的字符数量。setLength(newLength)方法设置字符串构建器的长度。如果参数 newLength 小于字符串构建器的当前长度,则字符串构建器会被截短到恰好能包含由参数 newLength 给定的字符个数。如果参数 newLength 大于或等于当前长度,则给字符串构建器追加足够多的空字符(’\u0000'), 使其长度 length 变成新参数 newLength。参数 newLength 必须大于等于 0。
charAt(index)方法返回字符串构建器中某个特定下标 index 的字符。下标是基于 0的,字符串构建器中的第一个字符的下标为 0, 第二个字符的下标为1,依此类推。参数 index 必须大于或等于 0, 并且小于字符串构建器的长度。
注意:字符串的长度总是小于或等于构建器的容量。长度是存储在构建器中的字符串的实际大小,而容量是构建器的当前大小。如果有更多的字符添加到字符串构建器,超出它的容量,则构建器的容量就会自动增加。在计算机内部,字符串构建器是一个字符數组,因此,构建器的容量就是数组的大小。如果超出构建器的容量,就用新的数组替換现有數组。新数组的大小为 2x( 之前数组的长度 +1)。
提示:可以使用 new StringBuilderCinitialCapacity)创建指定初始容量的 StringBuilder。通过仔细选择初始容量能够使程序更有效。如果容量总是超过构建器的实际使用长度,JVM 将永远不需要为构建器重新分配内存。另一方面,如果容量过大将会浪费内存空间。可以使用 trimToSizeQ 方法将容量降到实际的大小。