第4章 控制执行流程
就像有知觉的生物一样,程序必须在执行过程中控制它的世界,并做出选择。在Java中,我们要使用执行控制语句来做出选择。
在Java中,流程控制所涉及的关键字包括:if-else、while、do-while、for、return、break以及选择语句switch。然而,Java并不支持goto语句,但仍然可进行类似goto的跳转,不过相比goto有了更多限制。。
4.1 true和false
所有条件语句都利用条件表达式的真或假来决定执行路径。所有的关系操作符都可用于构造条件语句,其结果为一个布尔值。如果在布尔测试中使用一个非布尔值,那么必须用一个条件表达式将其转换成布尔值。
4.2 if-else
if-else语句是控制程序流程的最基本的形式,其中else是可选的,可按以下两种形式使用:
if(Boolean-expression)
statement
或
if(Boolean-expression)
statement
else
statement
布尔表达式必须产生一个布尔结果,statement指用分号结尾的简单语句或复合语句(封闭在花括号内的一组简单语句)。
通过下例,我们可以更清楚地了解if-else的使用:
public class IfElse {
static int result = 0;
static void test(int testval, int target) {
if (testval > target)
result = 1;
else if (testval < target)
result = -1;
else
result = 0;
}
public static void main(String[] args) {
test(10, 5);
print(result);
test(5, 10);
print(result);
test(5, 5);
print(result);
}
}
在test()方法中的 else if 并非新的关键字,其仅仅是在else后紧跟了另一个新的if-else语句。
尽管Java是格式自由的语言,但为了更加方便地确定起始与终止,还是将流程控制语句的主体部分进行缩进排列。
4.3 迭代
while、do-while和for用来控制循环,有时将它们划分为迭代语句(iteration statement)。语句会重复执行,直到起控制作用的布尔表达式(BooleanExpression)得到false的结果为止。
while循环在刚开始会计算一次布尔表达式的值,在语句的下一次迭代前会再次计算,格式如下:
while(Boolean-expression)
statement
下面的例子可一直产生随机数,直到符合特定条件为止:
public class WhileTest {
static boolean condition() {
boolean result = Math.random() < 0.99;
System.out.print(result + ", ");
return result;
}
public static void main(String[] args) {
while (condition())
System.out.println("Inside 'while'");
System.out.println("Exited 'while'");
}
}
上例使用了Math库里的静态方法random(),可产生0和1之间的一个double值。while的条件表达式表示的意思为:只要condition()返回的为true,即result为true,即产生的随机数小于0.99,就重复执行循环体中的语句。
4.3.1 do-while
do-while的格式如下:
do
statement
while(Boolean-expression);
从格式可以看出,do-while比while少进行一次表达式计算,即do-while循环体中的语句至少会执行一次。
4.3.2 for
for循环可能是最经常使用的迭代形式,其在第一次迭代前需进行初始化,随后进行条件测试,并且在每次迭代结束后,进行步进。 for循环格式如下:
for(initialization; Boolean-expression; step)
statement
for循环常用于执行计数任务:
public class ListCharacters {
public static void main(String[] args) {
for (char c = 0; c < 128; c++) {
if (Character.isLowerCase(c)) {
System.out.println("value: " + (int) c + " Character: " + c);
}
}
}
}
注意,Java是在整个块的范围内分散变量声明,在真正需要的地方加以定义。变量c在for循环的控制表达式里被定义,所以它的作用域就是for控制的表达式的范围内。
4.3.3 逗号操作符
在for循环的控制表达式中,我们可以使用逗号操作符,在表达式的初始化和步进部分,可以使用一系列由逗号分隔的语句使得语句独立执行。(并非逗号分隔符,逗号用作分隔符时用来分隔函数的不同参数。)
例如,我们通过使用逗号操作符,可以在for语句内定义多个相同类型变量:
public class CommaOperator {
public static void main(String[] args) {
for (int i = 1, j = i + 10; i < 5; i++, j = i * 2) {
System.out.println("i = " + i + " j = " + j);
}
}
}
从上例中可以看出:无论在初始化还是在步进部分,语句都是顺序执行的。此外,初始化部分可以定义任意数量的同一类型的变量。
4.4 Foreach语法
Java SE5引入了一种新的更加简洁的for语法用于数组和容器,即foreach语法:不必创建int变量去对由访问项构成的序列进行计数,foreach将自动产生每一项。
例如,我们需要选取一个float数组中每一个元素:
public class ForEachFloat {
public static void main(String[] args) {
Random random = new Random(66);
float f[] = new float[10];
for (int i = 0; i < f.length; i++) {
f[i] = random.nextFloat();
}
for (float x : f) {
System.out.println(x);
}
}
}
由于在组装数组时,必须按索引访问,所以,我们一般使用for循环进行组装数组,使用foreach进行遍历数组。我们可以看到foreach语法为:
for(float x : f)
statement
该语句定义了一个float类型的变量x,继而将每一个f数组中的元素赋值给x。
任何返回一个数组的方法都可以使用foreach。range()方法为自定义类Range中的静态方法,它可以自动生成恰当的数组:
public class ForEachInt {
public static void main(String[] args) {
for (int i : range(10))
printnb(i + " ");
print();
for (int i : range(5, 10))
printnb(i + " ");
print();
for (int i : range(5, 20, 3))
printnb(i + " ");
print();
}
}
foreach语法不仅在录入代码时可以节省时间,而且容易阅读。
4.5 return
在Java中有多个关键词表示无条件分支,它们只是表示这个分支无需任何测试即可发生。这些关键字包括:return、break、continue。
return关键词有两方面的用途:
- 指定一个方法的返回值。
- 导致当前方法退出,并返回那个值。
我们可以通过使用return改写前面的test()方法:
public class IfElse2 {
static int test(int testval, int target) {
if (testval > target)
return 1;
else if (testval < target)
return -1;
else
return 0;
}
public static void main(String[] args) {
print(test(10, 5));
print(test(5, 10));
print(test(5, 5));
}
}
对于方法返回值类型的不同,return语句有不同的限制:
-
返回值类型为void,即使不写return,在方法的结尾处也会有一个隐式的return。
-
返回值类型不是void,那么必须确保每一条代码路径都将返回一个该类型的值。
4.6 break和continue
在任何迭代语句的主体部分,都可用break和continue控制循环的流程。
- break:强行退出循环,不执行循环中剩余部分的语句。
- continue:停止当前的迭代,退回循环起始处,开始下一次迭代。
下例展示了break和continue在for和while循环中的作用:
public class BreakAndContinue {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
if (i == 74) break;
if (i % 9 != 0) continue;
System.out.print(i + " ");
}
System.out.println();
for (int i : range(100)) {
if (i == 74) break;
if (i % 9 != 0) continue;
System.out.print(i + " ");
}
System.out.println();
int i = 0;
while(true){
i ++;
int j = i * 27;
if(j == 1269) break;
if(i % 10 != 0) continue;
System.out.print(i + " ");
}
}
}
在for循环中,一旦i到达74,break语句就会中断循环。只要i不能被9整除,continue语句就会结束本次循环,直到能够整除时将值打印出来。
在while循环中的条件始终为true,我们称其为无限循环,循环内部有一个break可中断循环。
4.7 臭名昭著的goto
goto起源于汇编语言的程序控制:若条件A成立,跳转至这里,否则跳转到那里。 通过阅读由编译器最终生成的汇编代码,可以发现程序控制里包含了许多跳转。(Java编译器生成它自己的汇编代码,运行在Java虚拟机上而非直接运行在CPU硬件上。)
Java没有goto,但可以通过break和continue完成类似跳转的操作。实际上它们不是跳转,而是中断迭代语句的一种方式。它们和goto的相同之处在于,都使用了标签。
标签是后面跟有冒号的标识符:
label:
在Java中,标签起作用的唯一的地方就是迭代语句之前,用于在一个迭代语句中嵌套另一个迭代或者一个开关。
下面是标签用于for循环的例子:
public class LabeledFor {
public static void main(String[] args) {
int i = 0;
outer:
for (; ; ) {
inner:
for (; i<10; i++) {
System.out.println("i = " + i);
if(i == 2 ){
System.out.println("continue");
continue;
}
if(i == 3 ){
System.out.println("break");
i++;
break;
}
if(i == 7 ){
System.out.println("continue outer");
i++;
continue outer;
}
if(i == 8 ){
System.out.println("break outer");
break outer;
}
for (int k = 0; k < 5; k++) {
if(k == 3){
System.out.println("continue inner");
continue inner;
}
}
}
}
}
}
在使用标签后,break和continue分别的作用为:
- break label:结束标签为label的循环语句。
- continue label:停止标签为label的本次循环,跳到该循环的起始处,开始下一次循环。
注意,在抵达for循环末尾之前,递增表达式不会执行。
下面的例子展示了带标签的break以及continue语句在while循环中的用法:
public class LabeledWhile {
public static void main(String[] args) {
int i = 0;
outer:
while(true){
System.out.println("Outer while loop");
while(true){
i++;
System.out.println("i = " + i);
if (i == 1) {
System.out.println("continue");
continue;
}
if (i == 3) {
System.out.println("continue outer");
continue outer;
}
if (i == 5) {
System.out.println("break");
break;
}
if (i == 7) {
System.out.println("break outer");
break outer;
}
}
}
}
}
break与continue小结:
- 一般的continue会退回最内层循环的开头,并继续执行。
- 带标签的continue会到达标签位置,并重新进入标签后的循环。
- 一般的break会中断并跳出当前循环。
- 带标签的break会中断并跳出标签所指的循环。
在Java中需要使用标签的唯一原因就是:对多层循环嵌套进行控制。
4.8 switch
switch语句可以根据整数表达式的值,从一系列代码中选出一段去执行。格式如下:
switch(integral-selector){
case integral-value1 : statement; break;
case integral-value2 : statement; break;
case integral-value3 : statement; break;
case integral-value4 : statement; break;
case integral-value5 : statement; break;
// ...
default: statement;
}
其中,integral-selector(整数选择因子)是一个能够产生整数值的表达式,switch能将该表达式的结果与每个integral-value(整数值)相比较。若有相等的,则执行对应语句,若没有,则执行default语句。
case尾部的break是可选的:
- 以break结尾,可以使执行流程跳转至switch主体的末尾。
- 省略break,则会继续执行后面的case语句,直到遇到break或执行到default语句。
- default语句后是否省略break,switch语句在执行完default后都会结束。
switch语句是实现多路选择的一种干净利落的方法,限制是选择因子必须为整数值。但Java SE5的新特性enum也可以和switch协调工作。
下面的例子可随机生成字母,并判断它们是元音还是辅音字母:
public class VowelsAndConsonants {
public static void main(String[] args) {
Random rand = new Random(66);
for (int i = 0; i < 100; i++) {
int c = rand.nextInt(26) + 'a';
System.out.print((char) c + ", " + c + ": ");
switch (c) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
System.out.println("vowel");
break;
case 'y':
case 'w':
System.out.println("Sometimes a vowel");
break;
default:
System.out.println("consonant");
break;
}
}
}
}
在case语句中,使用单引号引起的字符也会产生用于比较的整数值。
从上例中可以看出,case语句能够堆叠在一起,为一段代码形成多重匹配,即只要符号多种条件的一种,就执行那段特别的代码。
4.9 总结
本章介绍了大多数编程语言都具有的基本特性:运算、操作符优先级、类型转换以及选择和循环等待。