05-Control-Flow
第五章 控制流
程序必须在执行过程中控制它的世界并做出选择。 在
Java 中,你需要执行控制语句来做出选择。
true 和false
所有的条件语句都利用条件表达式的“真”或“假”来决定执行路径。举例:
a == b
。它利用了条件表达式 ==
来比较 a
与 b
的值是否相等。 该表达式返回 true
或 false
。代码示例:
// control/TrueFalse.java
public class TrueFalse {
public static void main(String[] args) {
System.out.println(1 == 1);
System.out.println(1 == 2);
}
}
输出结果:
true false
通过上一章的学习,我们知道任何关系运算符都可以产生条件语句。 注意:在if(a != 0)
。
if-else
else
是可选的,因此可以有两种形式的 if
。代码示例:
if(Boolean-expression)
“statement”
或
if(Boolean-expression)
“statement”
else
“statement”
布尔表达式(Boolean-expression)必须生成statement
既可以是以分号 ;
结尾的一条简单语句,也可以是包含在大括号 {}
内的的复合语句 —— 封闭在大括号内的一组简单语句。 凡本书中提及“statement”一词,皆表示类似的执行语句。
下面是一个有关test()
方法可以告知你两个数值之间的大小关系。代码示例:
// control/IfElse.java
public class IfElse {
static int result = 0;
static void test(int testval, int target) {
if(testval > target)
result = +1;
else if(testval < target) // [1]
result = -1;
else
result = 0; // Match
}
public static void main(String[] args) {
test(10, 5);
System.out.println(result);
test(5, 10);
System.out.println(result);
test(5, 5);
System.out.println(result);
}
}
输出结果:
1
-1
0
注解:else if
并非新关键字,它仅是 else
后紧跟的一条新 if
语句。
迭代语句
while,false
,循环语句才会停止。
while
while(Boolean-expression)
statement
执行语句会在每一次循环前,判断布尔表达式返回值是否为 true
。下例可产生随机数,直到满足特定条件。代码示例:
// control/WhileTest.java
// 演示 while 循环
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'");
}
}
输出结果:
true, Inside 'while'
true, Inside 'while'
true, Inside 'while'
true, Inside 'while'
true, Inside 'while'
...________...________...________...________...
true, Inside 'while'
true, Inside 'while'
true, Inside 'while'
true, Inside 'while'
false, Exited 'while'
condition()
方法使用到了random()
。该方法的作用是产生
<
产生的true
或 false
。此处 while
条件表达式代表condition()
返回 false
时停止循环”。
do-while
do
statement
while(Boolean-expression);
false
, false
,那么循环体内的语句不会被执行。实际应用中,
for
for(initialization; Boolean-expression; step)
statement
初始化false
,则跳出
// control/ListCharacters.java
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);
}
}
输出结果(前
value: 97 character: a
value: 98 character: b
value: 99 character: c
value: 100 character: d
value: 101 character: e
value: 102 character: f
value: 103 character: g
value: 104 character: h
value: 105 character: i
value: 106 character: j
...
注意:变量
传统的面向过程语言如
上例使用了char
的值,还封装了一些有用的方法。例如这里就用到了静态方法 isLowerCase()
来判断字符是否为小写。
逗号操作符
在
// control/CommaOperator.java
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);
}
}
}
输出结果:
i = 1 j = 11
i = 2 j = 4
i = 3 j = 6
i = 4 j = 8
上例中i
和 j
。实际上,在初始化部分我们可以定义任意数量的同类型变量。注意:在
for-in 语法
forEach()
产生混淆,因此我称之为
// control/ForInFloat.java
import java.util.*;
public class ForInFloat {
public static void main(String[] args) {
Random rand = new Random(47);
float[] f = new float[10];
for(int i = 0; i < 10; i++)
f[i] = rand.nextFloat();
for(float x : f)
System.out.println(x);
}
}
输出结果:
0.72711575
0.39982635
0.5309454
0.0534122
0.16020656
0.57799757
0.18847865
0.4170137
0.51660204
0.73734957
上例中我们展示了传统
for(float x : f) {
这条语句定义了一个x
,继而将每一个 f
的元素赋值给它。
任何一个返回数组的方法都可以使用toCharArray()
,返回值类型为
// control/ForInString.java
public class ForInString {
public static void main(String[] args) {
for(char c: "An African Swallow".toCharArray())
System.out.print(c + " ");
}
}
输出结果:
A n A f r i c a n S w a l l o w
很快我们能在 集合 章节里学习到,
通常,
for(int i = 0; i < 100; i++)
正因如此,除非先创建一个onjava
包中封装了range()
方法可自动生成恰当的数组。
在 封装(Implementation Hiding)这一章里我们介绍了静态导入(static import
// control/ForInInt.java
import static onjava.Range.*;
public class ForInInt {
public static void main(String[] args) {
for(int i : range(10)) // 0..9
System.out.print(i + " ");
System.out.println();
for(int i : range(5, 10)) // 5..9
System.out.print(i + " ");
System.out.println();
for(int i : range(5, 20, 3)) // 5..20 step 3
System.out.print(i + " ");
System.out.println();
for(int i : range(20, 5, -3)) // Count down
System.out.print(i + " ");
System.out.println();
}
}
输出结果:
0 1 2 3 4 5 6 7 8 9
5 6 7 8 9
5 8 11 14 17
20 17 14 11 8
range()
方法已被 重载(重载:同名方法,参数列表或类型不同range()
方法有多种重载形式:第一种产生从range()
表明还可以递减。range()
无参方法是该生成器最简单的版本。有关内容会在本书稍后介绍。
range()
的使用提高了代码可读性,让
请注意,System.out.print()
不会输出换行符,所以我们可以分段输出同一行。
return
在
return
的这些特点来改写上例 IfElse.java
文件中的 test()
方法。代码示例:
// control/TestWithReturn.java
public class TestWithReturn {
static int test(int testval, int target) {
if(testval > target)
return +1;
if(testval < target)
return -1;
return 0; // Match
}
public static void main(String[] args) {
System.out.println(test(10, 5));
System.out.println(test(5, 10));
System.out.println(test(5, 5));
}
}
输出结果:
1
-1
0
这里不需要 else
,因为该方法执行到 return
就结束了。
如果在方法签名中定义了返回值类型为
break 和continue
在任何迭代语句的主体内,都可以使用
下例向大家展示
// control/BreakAndContinue.java
// Break 和 continue 关键字
import static onjava.Range.*;
public class BreakAndContinue {
public static void main(String[] args) {
for(int i = 0; i < 100; i++) { // [1]
if(i == 74) break; // 跳出循环
if(i % 9 != 0) continue; // 下一次循环
System.out.print(i + " ");
}
System.out.println();
// 使用 for-in 循环:
for(int i : range(100)) { // [2]
if(i == 74) break; // 跳出循环
if(i % 9 != 0) continue; // 下一次循环
System.out.print(i + " ");
}
System.out.println();
int i = 0;
// "无限循环":
while(true) { // [3]
i++;
int j = i * 27;
if(j == 1269) break; // 跳出循环
if(i % 10 != 0) continue; // 循环顶部
System.out.print(i + " ");
}
}
}
输出结果
0 9 18 27 36 45 54 63 72
0 9 18 27 36 45 54 63 72
10 20 30 40
[1] 在这个 for 循环中,i
的值永远不会达到 100,因为一旦 i
等于 74,break 语句就会中断循环。通常,只有在不知道中断条件何时满足时,才需要 break。因为 i
不能被 9 整除,continue 语句就会使循环从头开始。这使 i 递增)。如果能够整除,则将值显示出来。
[2] 使用 for-in 语法,结果相同。
[3] 无限 while 循环。循环内的 break 语句可中止循环。注意,continue 语句可将控制权移回循环的顶部,而不会执行 continue 之后的任何操作。 因此,只有当 i
的值可被 10 整除时才会输出。在输出中,显示值 0,因为 0%9
产生 0。还有一种无限循环的形式: for(;;)
。 在编译器看来,它与 while(true)
无异,使用哪种完全取决于你的编程品味。
臭名昭著的goto
一个源码级别跳转的
正如上述提及的经典情况,我们不应走向两个极端。问题不在
尽管
“标签”是后面跟一个冒号的标识符。代码示例:
label1:
对
label1:
outer-iteration {
inner-iteration {
// ...
break; // [1]
// ...
continue; // [2]
// ...
continue label1; // [3]
// ...
break label1; // [4]
}
}
[1] break 中断内部循环,并在外部循环结束。 [2] continue 移回内部循环的起始处。但在条件 3 中,continue label1 却同时中断内部循环以及外部循环,并移至 label1 处。 [3] 随后,它实际是继续循环,但却从外部循环开始。 [4] break label1 也会中断所有循环,并回到 label1 处,但并不重新进入循环。也就是说,它实际是完全中止了两个循环。
下面是
// control/LabeledFor.java
// 搭配“标签 break”的 for 循环中使用 break 和 continue
public class LabeledFor {
public static void main(String[] args) {
int i = 0;
outer: // 此处不允许存在执行语句
for(; true ;) { // 无限循环
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++; // 否则 i 永远无法获得自增
// 获得自增
break;
}
if(i == 7) {
System.out.println("continue outer");
i++; // 否则 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 标签
}
}
输出结果:
i = 0
continue inner
i = 1
continue inner
i = 2
continue
i = 3
break
i = 4
continue inner
i = 5
continue inner
i = 6
continue inner
i = 7
continue outer
i = 8
break outer
注意i==3
的情况下直接执行。在 i==7
的情况下,continue outer
语句也会到达循环顶部,而且也会跳过递增,所以它也是直接递增的。
如果没有
下面这个例子向大家展示了带标签的
// control/LabeledWhile.java
// 带标签的 break 和 conitue 在 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;
}
}
}
}
}
输出结果:
Outer while loop
i = 1
continue
i = 2
i = 3
continue outer
Outer while loop
i = 4
i = 5
break
Outer while loop
i = 6
i = 7
break outer
同样的规则亦适用于
-
简单的一个
continue 会退回最内层循环的开头(顶部 ) ,并继续执行。 -
带有标签的
continue 会到达标签的位置,并重新进入紧接在那个标签后面的循环。 -
break 会中断当前循环,并移离当前标签的末尾。 -
带标签的
break 会中断当前循环,并移离由那个标签指示的循环的末尾。
大家要记住的重点是:在
在
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 (整数选择因子)是一个能够产生整数值的表达式,
在上面的定义中,大家会注意到每个
下面这个例子可随机生成字母,并判断它们是元音还是辅音字母:
// control/VowelsAndConsonants.java
// switch 执行语句的演示
import java.util.*;
public class VowelsAndConsonants {
public static void main(String[] args) {
Random rand = new Random(47);
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 vowel");
break;
default: System.out.println("consonant");
}
}
}
}
输出结果:
y, 121: Sometimes vowel
n, 110: consonant
z, 122: consonant
b, 98: consonant
r, 114: consonant
n, 110: consonant
y, 121: Sometimes vowel
g, 103: consonant
c, 99: consonant
f, 102: consonant
o, 111: vowel
w, 119: Sometimes vowel
z, 122: consonant
...
由于 Random.nextInt(26)
会产生a
,即可产生小写字母。在
请注意
int c = rand.nextInt(26) + 'a';
此处 Random.nextInt()
将产生a
上。这表示 a
将自动被转换为c
当作字符打印,必须将其转型为
switch 字符串
// control/StringSwitch.java
public class StringSwitch {
public static void main(String[] args) {
String color = "red";
// 老的方式: 使用 if-then 判断
if("red".equals(color)) {
System.out.println("RED");
} else if("green".equals(color)) {
System.out.println("GREEN");
} else if("blue".equals(color)) {
System.out.println("BLUE");
} else if("yellow".equals(color)) {
System.out.println("YELLOW");
} else {
System.out.println("Unknown");
}
// 新的方法: 字符串搭配 switch
switch(color) {
case "red":
System.out.println("RED");
break;
case "green":
System.out.println("GREEN");
break;
case "blue":
System.out.println("BLUE");
break;
case "yellow":
System.out.println("YELLOW");
break;
default:
System.out.println("Unknown");
break;
}
}
}
输出结果:
RED
RED
一旦理解了
作为Math.random()
。 它是否产生从
下面是一个可能提供答案的测试程序。 所有命令行参数都作为args
的数组就会导致程序失败。 解决这个问题,我们需要预先检查数组的长度,若长度为""
替代;否则,选择 args
数组中的第一个元素:
// control/RandomBounds.java
// Math.random() 会产生 0.0 和 1.0 吗?
// {java RandomBounds lower}
import onjava.*;
public class RandomBounds {
public static void main(String[] args) {
new TimedAbort(3);
switch(args.length == 0 ? "" : args[0]) {
case "lower":
while(Math.random() != 0.0)
; // 保持重试
System.out.println("Produced 0.0!");
break;
case "upper":
while(Math.random() != 1.0)
; // 保持重试
System.out.println("Produced 1.0!");
break;
default:
System.out.println("Usage:");
System.out.println("\tRandomBounds lower");
System.out.println("\tRandomBounds upper");
System.exit(1);
}
}
}
要运行该程序,请键入以下任一命令:
java RandomBounds lower
// 或者
java RandomBounds upper
使用 onjava
包中的Math.random()
产生的随机值里不包含Math.random()
的结果集范围包含
本章小结
本章总结了我们对大多数编程语言中出现的基本特性的探索:计算,运算符优先级,类型转换,选择和迭代。 现在让我们准备好,开始步入面向对象和函数式编程的世界吧。 下一章的内容涵盖了