通常,源文件中的语句按照它们出现的顺序从头到尾执行。但是,控制流语句(control flow statement)通过使用决策、循环和分支来改变执行流程,使程序按照条件执行特定的代码块。这一节介绍Java编程语句提供的决策语句(if-then、if-then-else和switch)、循环语句(for、while和do-while)以及分支语句(break、continue和return)。
3.4.1 if-then和if-then-else语句
1. if-then语句
if-then是所有控制流语句中最基础的语句。只有特定检测结果为true时,它才通知程序执行特定代码段落。例如,只在自行车处于行进状态时,Bicycle类才允许使用刹车降低自行车的速度。下面是applyBrakes方法的一种可能实现:
void applyBrakes(){
if (isMoving){ // the "if" clause: bicycle must moving
currentSpeed--; // the "then" clause:
// decrease current speed
}
}
如果测试结果为false(就是说自行车不在行进状态),那么控制就跳转到if-then语句的结尾。
另外,如果then子句只包含一个语句,那么前后括号是可选的:
void applyBrakes(){
if (isMoving) currentSpeed--; // same as above,
// but without braces
}
决定什么时候省略括号是个人习惯问题。省略括号会导致代码更加脆弱。如果以后在then子句中加入第二个语句,常见的错误是忘记加上现在必需的括号。编译器不能捕获这样的错误;你将会得到错误的结果。
2. if-then-else语句
当if子句的计算结果为false时,if-then-else语句提供第二个执行路径。在applyBrakes方法中,如果自行车在没有行进时使用刹车,你可以使用if-then-else语句采取某些措施。在这个例子中,采取的措施是简单地输出错误消息,说明自行车已经停住了。
void applyBrakes(){
if (isMoving) {
currentSpeed--;
} else {
System.err.println("The bicycle has already stopped!");
}
}
下面的程序IfElseDemo根据考试成绩的值评出等级:如果得分为90或以上,则等级为A;如果得分80或以上,则为B,依此类推。
class IfElseDemo {
public static void main(String[] args) {
int testscore = 76;
char grade;
if (testscore >= 90) {
grade = 'A';
} else if (testscore >= 80) {
grade = 'B';
} else if (testscore >= 70) {
grade = 'C';
} else if (testscore >= 60) {
grade = 'D';
} else {
grade = 'F';
}
System.out.println("Grade = " + grade);
}
}
程序的输出为:
Grade = C
可能你注意到,testscore的值能满足复合语句中的多个表达式:76 >= 70和76 >= 60。但是,一旦满足条件,会执行适当语句(grade = 'C';),而不会计算其余条件。
3.4.2 switch语句
与if-then和if-then-else语句不同,switch语句允许使用任意数量的可能执行路径。switch语句可以处理byte、short、char和int基本数据类型。它也可以处理4.5节中讨论的枚举类型(enumerated type),还可以处理“包装”特定原始类型的几个特殊类:Character、Byte、Short和Integer,第8章中讨论这些类。
下面的程序SwitchDemo声明int类型的month变量,这个变量表示一年中的月份。程序使用switch语句,按照month的值输出月份名称。
class SwitchDemo {
public static void main(String[] args) {
int month = 8;
switch (month) {
case 1: System.out.println("January"); break;
case 2: System.out.println("February"); break;
case 3: System.out.println("March"); break;
case 4: System.out.println("April"); break;
case 5: System.out.println("May"); break;
case 6: System.out.println("June"); break;
case 7: System.out.println("July"); break;
case 8: System.out.println("August"); break;
case 9: System.out.println("September"); break;
case 10: System.out.println("October"); break;
case 11: System.out.println("November"); break;
case 12: System.out.println("December"); break;
default: System.out.println("Invalid month.");break;
}
}
}
在这个例子中,“August”被发送到标准输出。
switch的语句体被称为switch块(switch block)。switch块直接包含的任何语句都可以使用一个或者多个case或default标签加以标记。switch语句计算其表达式,然后执行适当的case语句。
当然,你也可以使用if-then-else语句实现相同操作:
int month = 8;
if (month == 1) {
System.out.println("January");
} else if (month == 2) {
System.out.println("February");
}
... // and so on
决定使用if-then-else语句还是switch语句,有时候是判断的问题。你可以根据可读性和其他因素决定采用哪种语句。If-then-else语句可以用于根据某范围的值或条件进行判断,而switch语句只用于根据单个整数值或者枚举值进行判断。
switch语句中另一个有意思的地方是每个case语句后面的break语句。break语句终止包含它的switch语句,控制流程从switch代码块后的第一个语句继续执行。break语句是必需的,因为如果没有break语句,将继续执行下面的case语句。也就是说,如果没有显式的break语句,流程控制将一个接一个地执行后续的case语句。下面的程序SwitchDemo2演示让case语句连续执行为什么可能是有用的:
class SwitchDemo2 {
public static void main(String[] args) {
int month = 2;
int year = 2000;
int numDays = 0;
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
numDays = 31;
break;
case 4:
case 6:
case 9:
case 11:
numDays = 30;
break;
case 2:
if ( ((year % 4 == 0) && !(year % 100 == 0))
|| (year % 400 == 0) )
numDays = 29;
else
numDays = 28;
break;
default:
System.out.println("Invalid month.");
break;
}
System.out.println("Number of Days = " + numDays);
}
}
程序的输出是:
Number of Days = 29
从技术上说,最后的break语句不是必需的,因为流程总会执行到switch语句之外。但是,我们建议在最后一个case语句中也使用break语句,以便更容易修改代码并减少出错的可能。default部分处理没有被任何case语句显式处理的所有值。
3.4.3 while和do-while语句
当特定条件为true时,while语句持续执行一个代码块。其语法可以表示为:
while (expression) {
statement(s)
}
while语句计算表达式expression,它必须返回一个布尔值。如果表达式的计算结果为true,那么while语句就执行while块中的语句statement(s)。while语句继续检测表达式并且执行它的块,直到表达式的计算结果为false。下面的程序WhileDemo使用while语句输出从1到10的值:
class WhileDemo {
public static void main(String[] args){
int count = 1;
while (count < 11) {
System.out.println("Count is: " + count);
count++;
}
}
}
还可以使用while语句实现无限循环,如下:
while (true){
// your code goes here
}
Java编程语言还提供do-while语句,可以表示为:
do {
statement(s)
} while (expression);
do-while和while语句之间的区别在于,do-while在循环结尾处(而不是开始处)计算其表达式。因此,do块中的语句总会至少执行一次,如下面的程序DoWhileDemo所示:
class DoWhileDemo {
public static void main(String[] args){
int count = 1;
do {
System.out.println("Count is: " + count);
count++;
} while (count <= 11);
}
}
3.4.4 for语句
for语句提供了对一个范围内的值进行迭代的紧凑方式。程序员经常把它称为“for循环”,这是因为它的运行方式是重复循环,直到满足特定条件为止。for语句的一般形式可以表示为:
for (initialization; termination; increment) {
statement(s)
}
在使用for语句的这个版本时,要牢记:
l Initialization(初始化)表达式对循环进行初始化;它在循环开始时执行一次。
l 当termination(终止)表达式计算结果为false时,循环终止。
l 在循环的每次迭代之后调用increment表达式;最常见的方式是使用这个表达式递增或者递减一个值。
下面的程序ForDemo使用for语句的一般形式把数字1到10发送到标准输出:
class ForDemo {
public static void main(String[] args){
for(int i=1; i<11; i++){
System.out.println("Count is: " + i);
}
}
}
这个程序的输出为:
Count is: 1
Count is: 2
Count is: 3
Count is: 4
Count is: 5
Count is: 6
Count is: 7
Count is: 8
Count is: 9
Count is: 10
注意初始化表达式中声明变量的代码。这个变量的作用域是从其声明开始,直到for语句控制块的结束,所以它也可以用在终止和递增表达式中。如果在循环之外不需要控制for语句的变量,那么最好在初始化表达式中声明变量。i、j和k这样的名称经常用于控制for循环;在初始化表达式中声明它们,就限制了它们的生存周期并且减少了错误的可能。
for循环的三个表达式都是可选的;可以像下面这样创建无限循环:
for ( ; ; ) { // infinite loop
// your code goes here
}
for语句还有另外一种形式,这种形式用于在集合(参见11章)和数组(参见3.1.3节)之中进行迭代。有时候把这种形式称为增强的for(enhanced for)语句,它可以使循环更加紧凑和容易阅读。为了演示,参考下面的数组,它保存数字1到10:
int[] numbers = {1,2,3,4,5,6,7,8,9,10};
下面的程序EnhancedForDemo使用增强for循环遍历这个数组:
class EnhancedForDemo {
public static void main(String[] args){
int[] numbers = {1,2,3,4,5,6,7,8,9,10};
for (int item : numbers) {
System.out.println("Count is: " + item);
}
}
}
在这个例子中,变量item保存从数字数组获得的当前值。这个程序的输出和前一个程序相同:
Count is: 1
Count is: 2
Count is: 3
Count is: 4
Count is: 5
Count is: 6
Count is: 7
Count is: 8
Count is: 9
Count is: 10
我们建议,只要情况允许,就使用for语句的这种形式,而不使用其一般形式。
3.4.5 分支语句
1. break语句
break语句有两种形式:带标签和无标签。在前面讨论switch语句时,你已经见到了无标签形式。
也可以使用无标签的break语句终止for、while或者do-while循环,如下面的程序BreakDemo所示:
class BreakDemo {
public static void main(String[] args) {
int[] arrayOfInts = { 32, 87, 3, 589, 12, 1076,
2000, 8, 622, 127 };
int searchfor = 12;
int i;
boolean foundIt = false;
for (i = 0; i < arrayOfInts.length; i++) {
if (arrayOfInts[i] == searchfor) {
foundIt = true;
break;
}
}
if (foundIt) {
System.out.println("Found " + searchfor +
" at index " + i);
} else {
System.out.println(searchfor + " not in the array");
}
}
}
这个程序在数组中搜索数字12。当找到这个值时,以粗体显示的break语句终止循环。然后控制流转到程序结尾的输出语句。
这个程序的输出是:
Found 12 at index 4
无标签break语句终止最内层的switch、for、while或do-while语句,而带标签的break语句终止外层语句。下面的程序BreakWithLabelDemo和前一个程序类似,但是它使用嵌套for循环在二维数组中搜索一个值。当找到这个值时,带标签break语句终止外层for循环(标签为“search”):
class BreakWithLabelDemo {
public static void main(String[] args) {
int[][] arrayOfInts = { { 32, 87, 3, 589 },
{ 12, 1076, 2000, 8 },
{ 622, 127, 77, 955 }
};
int searchfor = 12;
int i;
int j = 0;
boolean foundIt = false;
search:
for (i = 0; i < arrayOfInts.length; i++) {
for (j = 0; j < arrayOfInts[i].length; j++) {
if (arrayOfInts[i][j] == searchfor) {
foundIt = true;
break search;
}
}
}
if (foundIt) {
System.out.println("Found " +
searchfor + " at " + i + ", " + j);
} else {
System.out.println(searchfor + " not in the array");
}
}
}
程序的输出为:
Found 12 at 1, 0
break语句终止带标签的语句;它不把控制流转到标签位置。控制流被转到紧跟在带标签(被终止的)语句后面的语句。
2. continue语句
continue语句跳过for、while或者do-while循环的当前迭代。无标签形式跳到最内层循环体的结尾,并且计算控制循环的布尔表达式。下面的程序ContinueDemo遍历一个字符串,计算字母“p”的出现次数。如果当前字母不是p,continue语句就跳过循环的剩余部分,然后处理下一个字符。如果是p,程序就递增字母计数器。
class ContinueDemo {
public static void main(String[] args) {
String searchMe = "peter piper picked a peck of " +
"pickled peppers";
int max = searchMe.length();
int numPs = 0;
for (int i = 0; i < max; i++) {
//interested only in p's
if (searchMe.charAt(i) != 'p')
continue;
//process p's
numPs++;
}
System.out.println("Found " + numPs +
" p's in the string.");
}
}
这个程序的输出是:
Found 9 p's in the string.
为了更清楚地了解这种处理效果,可以删除continue语句,重新进行编译。再次运行程序时,计数器将是错误的,它指出找到了35个p,而不是9个。
带标签的continue语句跳过标有给定标签的外层循环的当前迭代。下面的示例程序ContinueWithLabelDemo使用嵌套的循环在一个字符串中搜索子字符串。这里需要两个嵌套的循环:一个迭代子字符串,另一个迭代正在被搜索的字符串。下面的程序ContinueWith- LabelDemo使用带标签的continue语句跳过外层循环的一次迭代。
class ContinueWithLabelDemo {
public static void main(String[] args) {
String searchMe = "Look for a substring in me";
String substring = "sub";
boolean foundIt = false;
int max = searchMe.length() - substring.length();
test:
for (int i = 0; i <= max; i++) {
int n = substring.length();
int j = i;
int k = 0;
while (n-- != 0) {
if (searchMe.charAt(j++) != substring.charAt(k++)) {
continue test;
}
}
foundIt = true;
break test;
}
System.out.println(foundIt ? "Found it" :
"Didn't find it");
}
}
下面是这个程序的输出:
Found it
3. return语句
最后一个分支语句是return语句。return语句退出当前方法,控制流返回到调用这个方法的位置。return语句有两种形式:一种返回一个值,另一种不返回值。要返回一个值,只需将此值(或一个计算出此值的表达式)放在return关键字后面:
return ++count;
return的返回值的数据类型必须匹配方法声明的返回值类型。当方法被声明为void时,使用return的不返回值形式:
return;
4.2.2节的第2小节将讨论编写方法所需的所有知识。
3.4.6 控制流语句小结
if-then是所有控制流语句中最基础的语句。它通知程序,只有特定检测结果为true时才执行特定代码段落。
if-then-else语句提供当if子句结果为false时的第二个执行路径。
与if-then和if-then-else语句不同,switch语句允许任意数量的可能执行路径。
当特定条件为true时,while和do-while语句持续执行一个语句块。
do-while和while之间的不同之处在于,do-while在循环的结尾处(而不是开始处)计算其表达式。因此,do块内的语句总会执行至少一次。
for语句提供对一个范围内的值进行迭代的紧凑方式。它有两种形式,其中一种用于遍历集合和数组。
问题和练习:控制流语句
问题
1. Java编程语句支持的最基础的控制流语句是___语句。
2. ___语句允许使用任意数量的可能执行路径。
3. ___语句和while语句类似,但是在循环的___计算其表达式。
4. 如何使用for语句编写无限循环?
5. 如何使用while语句编写无限循环?
练习
1. 查看下面的代码段。
if (aNumber >= 0)
if (aNumber == 0) System.out.println("first string");
else System.out.println("second string");
System.out.println("third string");
a. 如果aNumber为3,你认为代码将会输出什么结果?
b. 编写一个测试程序包含前面的代码段,使aNumber为3。这个程序的输出是什么?这是你预测的输出吗?解释输出为什么是这样;换句话说,代码段的控制流是怎样的?
c. 只使用空格和换行,重新格式化代码段,使控制流更易于理解。
d. 使用括号“{”和“}”,进一步使代码段更清楚明了。
答案
可以在以下位置找到“问题”和“练习”的答案:
tutorial/java/nutsandbolts/QandE/answers_flow.html