静态方法
。但是,这个解决方案是有局限性的。假设希望将一个日期和这个贷款联系起来。如果不使用对象的话,没有一个好的办法可以将一个日期和贷款联系起来。传统的面向过程式编程是动作驱动的,数据和动作是分离的。面向对象编程的范式重点在于对象,动作和数据一起定义在对象中。为了将日期和贷款联系起来,可以定义一个贷款类,将日期和贷款的其他属性一起作为数据域,并且贷款数据和动作集成在一个对象中。图10-2 给出了Loan类的UML类图。程序清单10-1 TestLoanClass.java
import java.util.Scanner; public class TestLoanClass { /** * Main method */ public static void main(String[] args) { //Create a Scanner Scanner input = new Scanner(System.in); //Enter annual interest rate System.out.print("Enter annual interest rate, for example, 8.25: "); double annualInterestRate = input.nextDouble(); //Enter number of years System.out.print("Enter number of years as an integer: "); int numberOfYears = input.nextInt(); //Enter loan amount System.out.print("Enter loan amount, for example , 120000.95"); double loanAmount = input.nextDouble(); //Create a Loan object Loan loan = new Loan(annualInterestRate, numberOfYears, loanAmount); //Display loan date, monthly payment , and total payment System.out.printf("The loan was created on %s\n" + "The monthly payment is %.2f\n The total payment is %.2f\n", loan.getLoanDate().toString(), loan.getMonthlyPayment(), loan.getTotalPayment()); } }
程序清单10-2 Loan.java
public class Loan { private double annualInterestRate; private int numberOfYears; private double loanAmount; private java.util.Date loanDate; /** * Default constructor */ public Loan() { this(2.5, 1, 1000); } /** * Construct a loan with specified annual interest rate, * number of years, and loan amount */ public Loan(double annualInterestRate, int numberOfYears, double loanAmount) { this.annualInterestRate = annualInterestRate; this.numberOfYears = numberOfYears; this.loanAmount = loanAmount; loanDate = new java.util.Date(); } /** * Return annualInterestRate */ public double getAnnualInterestRate() { return annualInterestRate; } /** * Set a new annualInterestRate */ public void setAnnualInterestRate(double annualInterestRate) { this.annualInterestRate = annualInterestRate; } /** * Return numberOfYears */ public int getNumberOfYears() { return numberOfYears; } /** * Set a new numberOfYears */ public void setNumberOfYears(int numberOfYears) { this.numberOfYears = numberOfYears; } /** * Return loanAmount */ public double getLoanAmount() { return loanAmount; } /** * Set a new loanAmount */ public void setLoanAmount(double loanAmount) { this.loanAmount = loanAmount; } /** * Find monthly payment */ public double getMonthlyPayment() { double monthlyInterestRate = annualInterestRate / 1200; double monthlyPayment = loanAmount * monthlyInterestRate / (1 - (1 / Math.pow(1 + monthlyInterestRate, numberOfYears * 12))); return monthlyPayment; } /** * Find total payment */ public double getTotalPayment() { double totalPayment = getMonthlyPayment() * numberOfYears * 12; return totalPayment; } /** * Return loan data */ public java.util.Date getLoanDate() { return loanDate; } }
面向过程的范式重点在于设计方法。面向对象的范式将数据和方法耦合在一起构成对象。使用面向对象范式的软件设计重点在于对象以及对象上的操作。
第1~8章介绍使用循环、方法和数组来解决问题的基本程序设计技术。这些技术的学习为面向对象程序设计打下了坚实的基础。类为构建可重用软件提供了更好的灵活性和模块化。本节使用面向对象方法来改进第3章中介绍的一个问题的解决方案。在这个改进的过程中,可以洞察面向过程程序设计和面向对象程序设计的不同,也可以看出使用对象和类来开发可重用代码的优势
程序清单3-4 给出了计算身体质量指数(BMI)的程序。因为它的代码在main方法中,所以不能在其他程序中重用。为使之具备可重用性,可以定义一个静态方计算身体质量指数,如下所示:
public static double getBMI(double weight, double height)
这个方法对于计算给定体重和身高的身体质量指数是有用的。但是,它是有局限性的。假设需要将体重和身高同一个人的名字与出生日期相关联,虽然可以分别声明几个变量来存储这些值,但是这些值不是紧密耦合在一起的。将它们耦合在一起的理想方法就是创建一个将它们全部包含的对象。因为这些值都被绑定到单独的对象上,所以它们应该存储在实例数据域中。可以定义一个名为BMI的类,如图10-3所示。
假设BMI类是可用的。程序清单10-3给出使用这个类的测试程序。
程序清单10-3 UseBMIClass.java
public class UseBMIClass { public static void main(String[] args) { BMI bmi1 = new BMI("Kim Yang", 18 , 145,70); System.out.println("The BMI for " + bmi1.getName() + " is " + bmi1.getBMI() + " " + bmi1.getStatus()); BMI bmi2 = new BMI("Susan King", 215, 70); System.out.println("The BMI for " + bmi2.getName() + " is " + bmi2.getBMI() + " " + bmi2.getStatus()); } }
程序清单10-4 BMI.java
public class BMI { private String name; private int age; //in pounds private double weight; //in inches private double height; public static final double KILOGRAMS_PER_POUND = 0.45359237; public static final double METERS_PER_INCH = 0.0254; public BMI(String name, int age, double weight, double height){ this.name = name; this.age = age; this.weight = weight; this.height = height; } public BMI(String name , double weight, double height){ this(name, 20, weight , height); } public double getBMI(){ double bmi = weight * KILOGRAMS_PER_POUND / ((height * METERS_PER_INCH) * (height * METERS_PER_INCH)); return Math.round(bmi * 100) / 100.0; } public String getStatus(){ double bmi = getBMI(); if (bmi < 18.5){ return "Underweight"; } else if (bmi < 25) { return "Normal"; } else if (bmi < 30) { return "Overweight"; }else { return "Obese"; } } public String getName(){ return name; } public int getAge() { return age; } public double getWeight() { return weight; } public double getHeight() { return height; } }
聚集是关联的一种特殊形式,代表了两个对象之间的归属关系。聚集对has - a 关系进行建模。所有者对象称为聚集对象,它的类称为聚集类。而从属对象称为被聚集对象,它的类称为被聚集类。
如果被聚集对象的存在依赖于聚集对象,我们称这个两个对象之间的关系为组合(composition)。换句话说,被聚集对象不能单独存在。例如:“一个学生有一个名字”就是学生类Student 与名字类Name 之间的一个组合关系,因为Name 依赖于 Student ; 而“一个学生有一个地址”是学生类Student 与地址类Address 之间的一个聚集关系,因为一个地址自身可以单独存在。组合暗示了独占性的拥有。一个对象拥有另外一个对象。当拥有者对象销毁了,依赖对象也会销毁。在UML中,附加在聚集类上的实心菱形表示它和被聚集类之间具有组合关系;而附加在聚集类上的空心菱形表示它与被聚集类之间具有聚集关系,如图10-6所示
在图10-6中,每个学生只能有一个地址,而每个地址最多可以被3个学生共享。每个学生都有一个名字,而每个学生的名字都是唯一的。
聚集关系通常被表示为聚集类中的一个数据域。例如:图10-6 中的关系可以使用图10-7 中的类来实现。关系“一个学生拥有一个名字”以及“一个学生有一个地址”在Student 类中的数据域name和address中实现。
聚集可以存在于同一类的对象之间。例如:一个人可能有一个管理者,如图10-8所示。
在关系“一个人有一个管理者”中,管理者可以如下表示为Person类的一个数据域:
public class Person(){ //The type of the data is the class itself private Person supervisor; ... }
如果一个人可以有几个管理者,如图10-9a所示,可以用一个数组存储管理者,如图10-9b所示。
由于聚集和组合关系都以同样的方式用类来表示,为了简单起见,我们不区分它们,将两者都称为组合。
程序清单 10-5 TestCourse.java
public class TestCourse { public static void main(String[] args) { Course course1 = new Course("Data Structures"); Course course2 = new Course("Database Systems"); course1.addStudent("Peter Jones"); course1.addStudent("Kim Smith"); course1.addStudent("Anne Kennedy"); course2.addStudent("Peter Jones"); course2.addStudent("Steve Smith"); System.out.println("Number of students in course1: " + course1.getNumberOfStudents()); String[] students = course1.getStudents(); for (int i = 0; i < course1.getNumberOfStudents(); i++) { System.out.print(students[i] + ", "); } System.out.println(); System.out.print("Number of students in course2: " + course2.getNumberOfStudents()); } }
程序清单 10-6 Course.java
public class Course { private String courseName; private String[] students = new String[100]; private int numberOfStudents; public Course(String courseName) { this.courseName = courseName; } public void addStudent(String student) { students[numberOfStudents] = student; numberOfStudents++; } public String[] getStudents() { return students; } public int getNumberOfStudents() { return numberOfStudents; } public String getCourseName() { return courseName; } public void dropStudent(String student) { //Left as exercise in Programing Exercise 10.9 } }
程序清单 10-7 TestStackOfIntegers.java
public class TestStackOfIntegers { public static void main(String[] args) { StackOfIntegers stack = new StackOfIntegers(); for (int i = 0; i < 10; i++) { stack.push(i); } while (!stack.empty()) { System.out.print(stack.pop() + " "); } } }
程序清单 10-8 StackOfIntegers.java
public class StackOfIntegers { private int[] elements; public int size; public static final int DEFAULT_CAPACITY = 16; /** * Construct a stack with the default capacity 16 */ public StackOfIntegers(){ this(DEFAULT_CAPACITY); } /** * Construct a stack with the specified maximum capacity */ public StackOfIntegers(int capacity){ elements = new int[capacity]; } /** * Push a new integer to the top of the stack */ public void push(int value){ if (size >= elements.length){ int[] temp = new int[elements.length * 2]; System.arraycopy(elements,0,temp,0,elements.length); elements = temp; } elements[size++] = value; } /** * Return and remove the top element from the stack */ public int pop(){ return elements[--size]; } /** * Return the top element from the stack */ public int peek(){ return elements[size - 1]; } /** * Test whether the stack is empty */ public boolean empty(){ return size == 0; } /** * Return the number of elements in the stack */ public int getSize(){ return size; } }
基本数据类型不是对象,但是可以使用Java API 中的包装类来包装成一个对象。
出于对性能的考虑,在Java中基本数据类型不作为对象使用。因为处理对象需要额外的系统开销,所以,如果将基本数据类型当作对象,就会给语言性能带来负面影响。然而,Java中的许多方法需要将对象作为参数。Java提供了一个方便的方法,即将基本数据类型合并为或者说包装成对象(例如,将int类包装成Integer类,将double类包装成Double类,将char包装成Character类)。通过使用包装类,可以将基本数据类型值作为对象处理。Java在java.lang包里为基本数据类型提供了Boolean、Character、Double、Float、Byte、Short、Integer和Long等包装类。Boolean类包装了布尔值true或者false。本节使用Integer和Double类为例介绍数值包装类。
大多数基本类型的包装类的名称与对应的基本数据类型名称一样,第一个字母要大写。对应int 的Integer 和对应char的Character 例外。
数值包装类相互之间都非常相似。每个都各自包含了doubleValue( )、floatValue( )、intValue( )、longValue( )、shortValue( )和byteValue( )等方法。这些方法将对象”转换“为基本类型值。Integer类和Double类的主要特征如图10-14所示。
既可以用基本数据类型值也可以用表示数值的字符串来构造包装类。例如,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.79769313486237570e + 308d):
System.out.println("The maximum integer is " + Integer.MAX_VALUE); System.out.println("The maximum positive float is " + Float.MIN_VALUE); System.out.println("The maximum double - precision floating -point numebr is " + Double.MAX_VALUE);
每个数值包装类都包含各自方法doubleValue( )、floatValue( )、intValue( )、longValue( )和shortValue( )。这些方法返回包装对象对应的double、float、int、long或short值。例如:
new Double(12.4).intValue() return 12; new Integer(12).doubleValue() return 12.0;
回顾下String类中包含compareTo方法用于比较两个字符串。数值包装类中包含compareTo方法用于比较两个数值,并且如果该数值大于、等于、小于另外一个数值时,分别返回1、0、1 。例如,
new Double(12.4).compareTo(new Double(12.3)) return 1; new Double(12.3).compareTo(new Double(12.3)) return 0; new Double(12.3).compareTo(new Double(12.51)) return -1;
数值包装类有一个有用的静态方法valueOf(String s)。该方法创建一个新对象,并将它初始化为指定字符串表示的值。例如,
Double doubleObject = Double valueOf("12.4"); Integer integerObject = Integer.valueOf("12");
我们已经使用过Integer类中的parseInt方法将一个数值字符串转换为一个int值,也使用过Double类中的parseDouble方法将一个数值字符串转变为一个double值。每个数值包装类都有两个重载方法,将数值字符串转换为正确的以10(十进制)或指定值为基数的数值。
//These two methods are in the Byte class public static byte parseByte(String s); public static byte parseByte(String s, int radix); //These two methods are in the Short class public static short parseShort(String s); public static short parseShort(String s, int radix); //These two methods are in the Integer class public static int parseInt(String s); public static int parseInt(String s, int radix); //These two methods are in the Long class public static long parseLong(String s); public static long parseLong(String s, int radix); //These two methods are in the Float class public static float parseFloat(String s); public static float parseFloat(String s, int radix); //These two methods are in the Double class public static double parseDouble(String s); public static double parseDouble(String s, int radix);
例如:
Integer.parseOmy("11",2) return 3; Integer.parseOmy("12",8) return 10; Integer.parseOmy("13",10) return 13; Integer.parseOmy("1A",16) return 26;
Integer.parseInt(“12”, 2)会引起一个运行时错误,因为12不是二进制数。
注意,可以使用format方法将一个十进制数转换为十六进制数,例如,
String.format("%x", 26) return 1A;
根据上下文环境,基本数据类型值可以使用包装类自动转换成一个对象,反之也可以。
将基本类型值转换为包装类对象的过程称为装箱(boxing)。相反的转换过程称为拆箱(unboxing)。Java运行基本类型和包装类类型之间进行自动转换。如果一个基本的类型值出现在需要对象的环境中,编译器会将基本类型值进行自动装箱;如果一个对象出现在需要基本类型值的环境中,编译器会将对象进行自动拆箱。这称为自动装箱和自动拆箱。
例如,可以用自动装箱将图a中的语句简化为图b中的语句:
由于可以自动拆箱,下面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值,然后相加。
BigInteger 类和 BigDiecimal 类可用于表示任意大小和精度的整数或者十进制数
如果要进行非常大的数的计算或者高精度浮点值的计算,可以使用 java.math包中的 BigInteger类 和 BigDiecimal 类。它们都是不可变的。long类型的最大整数值为long.MAX_VALUE(即9 223 372 036 854 775 807),而BigInteger 的实例可以表示任意大小的整数。可以使用new BigInteger(String) 和 new BigDecimal(String) 来创建 BigInteger 和 BigDiecimal 的实例,使用add、subtract、multiple、divide和remainder 方法进行算术运算, 使用compareTo 方法比较两个大数字。例如,下面的代码创建了两个 BigInteger 对象并且将它们进行相乘:
BigInteger a = new BigInteger("9223372036854775807"); BigInteger b = new BigInteger("2"); BigInteger c = a.multiply(b); //9223372036854775807 * 2 System.out.println(c);
输出为18446744073709551614
BigDiecimal 对象可以达到任意精度。如果不能终止运行,那么divide 方法会抛出ArithmeticException 异常。但是,可以使用重载的divide(BigDecimal d, int scale, int roundingMode)方法来指定scale 值和舍入方式来避免这个异常,这里的scale 是指小数点后最小的整数位数。例如,下面的代码创建BigDecimal 对象并做除法,其中scale 值为20,舍入方式为 BigDecimal.ROUND_UP。
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.3333 3333 3333 3333 3334
注意,一个整数的阶乘可能会非常大。程序清单10-9给出可以返回任意整数的阶乘的方法。
程序清单 10-9 StackOfIntegers.java
import java.math.BigInteger; import java.util.Scanner; public class LargeFactorial { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.print("Enter an integer: "); int n = input.nextInt(); System.out.println(n + " ! is \n" + factorial(n)); } public static BigInteger factorial(long n) { BigInteger result = BigInteger.ONE; for (int i = 0; i < n; i++) { result = result.multiply(new BigInteger(i + "")); } return result; } }
可以用字符串字面值或字符数组创建一个字符串对象。使用如下语法,用字符串字面值创建一个字符串:
String newString = new String(stringLitera);
参数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',' ','D','a','y'}; String message = new String (charArray);
String 变量存储的是对String 对象的引用, String 对象里存储的才是字符串的值。严格地讲, 术语String 变量、String 对象和字符串值是不同的。但在大多数情况下,它们之间的区别是可以忽略的。为简单起见,经常使用术语字符串表示String 变量、String 对象和字符串的值。
String 对象是不可变的,它的内容是不能改变的。下列代码会改变字符串的内容吗?
String s = "Java"; s = "HTML";
答案是不能。第一条语句创建了一个内容为"Java"
的String 对象,并将其引用赋值给s。第二条语句创建了一个内容为"HTML"的新String 对象,并将其引用赋值给s。赋值后第一个String 对象仍然存在,但是不能再访问它了,因为变量s现在指向了新的对象,如图10-15所示。
因为字符串在程序设计中是不可变的,但同时又会频繁地使用,所以Java虚拟机为了提高效率并节约内存,对具有相同字符序列的字符串字面值使用同一个实例。这样的实例称为驻留的(interned)字符串。例如,下面的语句:
String s1 = "Welcome to Java"; String s1 = new String ("Welcome to Java"); String s1 = "Welcome to Java"; System.out.println("s1 == s2 is " + (s1 == s2)); System.out.println("s1 == s3 is " + (s1 == s3));
程序结构显示:
s1 == s2 is false s1 == s3 is true
在上述语句中,由于s1 和s3 指向相同的驻留字符串 “Welcome to Java”, 因此,s1 == s3 为true。但是s1 == s2 为false,这是因为尽管s1和s2的内容相同,但它们是不同的字符串对象。
String 类提供了替换和拆分字符串的方法,如图10-16所示。
一旦创建了字符串,它的内容就不能改变。但是,方法replace、replaceFirst 和 replaceAll 会返回一个源自原始字符串的新字符串(并未改变原始字符串!)。方法replace 有好几个版本,它们实现用新的字符或子串替换字符串中的某个字符或子串。
例如:
"Welcome".replace('e','A') 返回一个新的字符串,WAlcomA. "Welcome".replaceFirst('e','AB') 返回一个新的字符串,WABlcome. "Welcome".replace('e','AB') 返回一个新的字符串,WABlcomAB. "Welcome".replace('el','AB') 返回一个新的字符串,WABcomA.
split 方法可以使用指定的分隔符从字符串中提取标记。例如,下面的代码:
String[] tokens = "Java#HTML#Perl".split("#"); for(int i = 0 ; i < tokens.length; i++) System.out.print(tokens[i] + " ");
显示
Java HTML Perl
我们经常需要编写代码来验证用户输入,比如检测输入是否是一个数字,或者是否是一个全部小写字母的字符串,或者是否是一个社会安全号。如何编写这类代码呢?一个简单有效地完成该任务的方法是使用正则表达式
正则表达式(regular expression)(缩写regex)是一个字符串,用于描述匹配一个字符集的模式。可以通过指定某个模式来匹配、替换或拆分一个字符串。这是一种非常有用且功能强大的特性。
从String 类中的matches 方法开始。乍一看,matches方法和equals 方法非常相似。例如,下面两条语句的值均为true:
"Java".matches("Java"); "Java".equals("Java");
在前面语句中的"Java.*"是一个正则表达式。它不仅可以匹配固定的字符串,还能匹配一组遵从某种模式的字符串。例如,下面语句的求值结构均为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);
这里,正则表达式[$+#] 指定匹配$、+ 或者#的模式。所以,输出是a NNN b NNN NNN c。
下面的语句以标点符号作为分隔符,将字符串拆分组成字符串数组。
C# tyokens = Java,C?C#,C++".split("[.,:;?]"); for (inn i = 0; i < tokens.length; i++) System.out.println(tokens[i]);
这里,正则表达式[. , : ; ?] 指定匹配 .
、,
,:
,;
或者?
的模式。这里的每个字符都是拆分字符串的分隔符。因此,这个字符串就被拆分成Java、C、C#和C++,并保存在数组tokens 中。正则表达式对起步阶段的学生来讲可能会比较复杂。基于这个原因,本节只介绍了两个简单的模式。若要进一步学习,请参照补充材料H。
字符串不是数组,但是字符串可以转换成数组,反之亦然。为了将字符串转换成一个字符数组,可以使用toCharArray方法。例如,下述语句将字符串"Java"转换成一个数组:
char[] chars = "Java".toCharArray();
因此,chars[0]为'J'
,chars[1]为'a'
。chars[2]为'v'
,chars[3]为'a'
还可以使用方法getChars(int sreBegin, 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 [])或者方法valueOf(char[])。例如,下面的语句使用String 构造方法由一个数组构造一个字符串:
String str = new String(new char[]{'J','a','v','a'});
下面的语句使用valueOf方法由一个数组构造一个字符串:
String str = String.valueOf(new char[] {'J','a','v','a'});
'5'
、'.'
、'4'
和'4'
构成的字符串。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-19 中列出的方法,在字符串构建器的末尾追加新内容,在字符串构建器的特定位置插入新的内容,还可以删除或替换字符串构建器中的字符。
StringBuilder 类提供了几个重载方法,可以将boolean、char、char[]、double、float、int、long和String类型值追加到字符串构建器。例如,下面的代码将字符串和字符追加到stringBuilder,构成新的字符串"Welcome to Java"。
StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("Welcome"); stringBuilder.append(' '); stringBuilder.append("to"); stringBuilder.append(' '); stringBuilder.append("Java");
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”。
stringBuilder.delete(8,11)将构建器变为Welcome Java。 stringBuilder.deleteCharAt(8)将构建器变为Welcome o Java。 stringBuilder.reverse()将构建器变为avaJ ot emocleW。 stringBuilder.replace(11,15,"HTML")将构建器变为Welcome to HTML。 stringBuilder.setCharAt(0,'w')将构建器变为welcome to Java。
除了setCharAt方法之外,所有这些进行修改的方法都做两件事:
例如,下面的语句
StringBuilder stringBuilder = stringBuilder.reverse();
将构建器中的字符倒置并把构建器的引用复制给stringBuilder1。这样,stringBuilder 和stringBuilder1 都指向同一个StringBuffer 对象。回顾一下,如果对方法的返回值不感兴趣,也可以将带返回值的方法作为语句调用。在这种情况下,Java 就简单地忽略掉返回值。例如,下面的语句
stringBuilder.reverse();
它的返回值就被忽略了。返回StringBuilder 的引用可以使得StringBuilder 方法形成调用链,如下所示:
stringBuilder.reverse().delete(8,11).replace(11,15,"HTML");
如果一个字符串不需要任何改变,则使用String 而不要使用StringBuilder。String 比StringBuilder 更加高效
程序清单 10-10 StackOfIntegers.java
package Based_On_Article.The_Textbook_Source_Code.Chapter10; import java.util.Scanner; public class PalindromeIgnoreNonAlphanumeric { /** * Main method */ public static void main(String[] args) { //Create a Scanner Scanner input = new Scanner(System.in); //Prompt the user yp enter a string System.out.print("Enter a string: "); String s = input.nextLine(); //Display result System.out.println("Ignoring nonalphanumeric characters, \nis " + s + " a palindrome? " + isPalindrome(s)); } /** * Return true if a string is a palindrome */ public static boolean isPalindrome(String s) { //Create a new string by eliminating nonalphanumeric chars String s1 = filter(s); //Create a new string that is the reversal of s1 String s2 = reverse(s1); //Check if the reversal is the same as the original string return s2.equals(s1); } /** * Create a new String by eliminating nonalphanumeric chars */ public static String filter(String s) { //Create a string builder StringBuilder stringBuilder = new StringBuilder(); //Examine each char in the string to skip alphanumeric char for (int i = 0; i < s.length(); i++) { if (Character.isLetterOrDigit(s.charAt(i))) { stringBuilder.append(s.charAt(i)); } } //Return a new filtered string return stringBuilder.toString(); } /** * Create a new string by reversing a specified string */ public static String reverse(String s) { StringBuilder stringBuilder = new StringBuilder(s); //Invoke reverse in StringBuilder stringBuilder.reverse(); return stringBuilder.toString(); } }