目录
第4章 控制执行流程
4.1 true和false
所有条件语句都利用条件表达式的真或假来决定执行路径。这里有一个条件表达式的例子:a==b。它用条件操作符“==”来判断a值是否等于b值。该表达式返回true或false。
本章前面介绍的所有关系操作符,都可拿来构造条件语句。注意Java不允许我们将一个数字作为布尔值使用,虽然这在C和C++里是允许的(在这些语言里,“真”是非零,而“假”是零)。
如果想在布尔测试中使用一个非布尔值,比如在if(a)中,那么首先必须用一个条件表达式将其转换成布尔值,例如if(a!=0)。
4.2 if-else
if-else语句是控制程序流程的最基本形式。其中if必需,而else是可选的。
语法如下:
if(Boolean-expression)
statement
if(Boolean-expression)
statement
else
statement
布尔表达式Boolean-expression必须产生一个布尔结果(true或false),statement指用分号结尾的简单语句,或复合语句─--封闭在花括号内的一组简单语句。
package 第4章_控制执行流程.第2节_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);
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语句。
4.3 迭代
while、do-while和for用来控制循环,有时将它们划分为迭代语句(iteration statement)。语句会重复执行,直到起控制作用的布尔表达式(Booleanexpression)得到“假”的结果为止。while循环的格式如下:
while(Boolean-expression)
statement
在循环刚开始时,会计算一次布尔表达式的值﹔而在语句的下一次迭代开始前会再计算一次。
下面这个简单的例子可产生随机数,直到符合特定的条件为止:
package 第4章_控制执行流程.第3节_迭代;
public class WhileTest {
static boolean condition() {
// Math.random()产生0到1之间(包括0但不包括1)的一个double值
boolean result = Math.random() < 0.99;
System.out.println(result + ", ");
return result;
}
public static void main(String[] args) {
while (condition()) {
System.out.println("Inside 'while'");
}
System.out.println("Exited 'while'");
}
}
/**
* 打印结果:
* 自己执行查看结果
*/
4.3.1 do-while
do-while的格式如下:
do
statement
while(Boolean-expression);
while和do-while唯一的区别就是do-while中的语句至少会执行一次,即便表达式第一次就被计算为false。而在while循环结构中,如果条件第一次就为false,那么其中的语句根本不会执行。在实际应用中,while比do-while更常用一些。
package 第4章_控制执行流程.第3节_迭代.第1目_dowhile;
public class DoWhile {
public static void main(String[] args) {
/* 1.do-while的使用 */
System.out.println("do-while的使用: ");
int i = 5;
do {
System.out.println(i);
i--;
} while (i != -1);
/* 2.while的使用 */
System.out.println("while的使用: ");
int j = 5;
while (j != -1) {
System.out.println(j);
j--;
}
}
}
/**
* 打印结果:
* do-while的使用:
* 5
* 4
* 3
* 2
* 1
* 0
* while的使用:
* 5
* 4
* 3
* 2
* 1
* 0
*/
4.3.2 for
for循环可能是最经常使用的迭代形式,这种在第一次迭代之前要进行初始化。随后,它会进行条件测试,而且在每一次迭代结束时,进行某种形式的“步进”。for循环的格式如下:
for(initialization; Boolean-expression; step)
statement
初始化(initialization)表达式、布尔表达式(Boolean-expression),或者步进(step)运算,都可以为空。每次迭代前会测试布尔表达式。若获得的结果是false,就会执行for语句后面的代码行。每次循环结束,会执行一次步进。
package 第4章_控制执行流程.第3节_迭代.第2目_for;
public class ListCharacters {
public static void main(String[] args) {
// for循环常用于执行"计数"任务
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
* ...
*/
注意:变量c是定义在for循环的控制表达式里的,而不是在main()开始的地方,也就是说变量c的作用域在for控制的表达式的范围内。
isLowerCase()方法来检查问题中的字符是否为小写字母。
4.3.3 逗号操作符
注意是逗号操作符,不是逗号分隔符(逗号分隔符用来分隔函数的不同参数)。
Java里唯一用到逗号操作符的地方就是for循环的控制表达式。在控制表达式的初始化和步进控制部分,可以使用一系列由逗号分隔的语句,而且那些语句均会独立执行。
通过使用逗号操作符,可以在for语句内定义多个变量,但是它们必须具有相同的类型。
package 第4章_控制执行流程.第3节_迭代.第3目_逗号操作符;
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
*/
for语句中的int定义涵盖了i和j,在初始化部分实际上可以拥有任意数量的具有相同类型的变量定义。在一个控制表达式中,定义多个变量的这种能力只限于for循环适用,在其他任何选择或迭代语句中都不能使用这种方式。
4.4 foreach语法
foreach语法,不必创建int变量去对访问项构成的序列进行计数,会自动产生每一项。
package 第4章_控制执行流程.第4节_foreach语法;
import java.util.Random;
public class ForEachFloat {
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
*/
其中可以看到foreach的语句语法:
for(float x: f){
}
其中f可以是一个数组,也可以是一个容器如List,而float x可以是一个基本类型的变量,也可以是对象。
任何返回一个数组的方法都可以使用foreach,例如String类有一个toCharArray()方法返回一个char数组:
package 第4章_控制执行流程.第4节_foreach语法;
public class ForEachString {
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
*/
在后面还会接触到foreach用于任何Iterable对象。
许多for语句会在一个整型值序列中步进,如
for(int i=0;i<100;i++)
但foreach不能起作用,所以可以使用net.mindview.util.Range包中创建的一个名为range()的方法,可以自动生成数组。
package 第4章_控制执行流程.第4节_foreach语法;
import static net.mindview.util.Print.print;
import static net.mindview.util.Print.printnb;
import static net.mindview.util.Range.range;
public class ForEachInt {
public static void main(String[] args) {
for (int i : range(10)) {// 0~9
printnb(i + " ");
}
print();
for (int i : range(5, 10)) { // 5~9
printnb(i + " ");
}
print();
for (int i : range(5, 20, 3)) { // 5~20 step 3
printnb(i + " ");
}
print();
}
}
/**
* 打印结果:
* 0 1 2 3 4 5 6 7 8 9
* 5 6 7 8 9
* 5 8 11 14 17
*/
- range(n):表示从0开始产生值,直至范围的上限n,但不包括该上限。如range(10)表示0到9。
- range(start, end):表示从第一个值start开始产生值,直至比第二个值end小1的值为止。如range(5,10)表示5到9。
- range(start, end, step):在第二个方法上有一个步进值step,是增量。如range(5,20,3)表示从5到17,每次以加3步进。
- printnb():表示打印不换行。
推荐使用foreach语法。
4.5 return
return关键词有两方面的用途:
- 一方面指定一个方法返回什么值(假设它没有void返回值)。
- 另一方面它会导致当前的方法退出,并返回那个值。
改写上面的test()方法,使用return返回值:
package 第4章_控制执行流程.第5节_return;
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) {
System.out.println(test(10, 5));
System.out.println(test(5, 10));
System.out.println(test(5, 5));
}
}
/**
* 打印结果:
* 1
* -1
* 0
*/
注意:如果在返回void的方法中没有return语句,那么在该方法的结尾处会有一个隐式的return,因此在方法中并非总是必须要有一个return语句。但是,如果一个方法声明它将返回void之外的其他东西,那么必须确保每一条代码路径都将返回一个值。
4.6 break和continue
break和continue语句用于迭代语句中控制循环的流程,其中
- break用于强制退出循环,不执行循环中剩余的语句。
- continue则停止执行当前的迭代,然后退出循环起始处,开始下一次迭代。
package 第4章_控制执行流程.第6节_break和continue;
import static net.mindview.util.Range.range;
public class BreakAndContinue {
public static void main(String[] args) {
// 1.break和continue在for中的使用
for (int i = 0; i < 100; i++) {
if (i == 74) break;// 直接退出循环,不再执行剩余语句
if (i % 9 != 0) continue; // 跳出当前循环,执行下一次迭代
System.out.print(i + " ");
}
System.out.println();
// 2.break和continue在foreach中的使用
for (int i : range(100)) {
if (i == 74) break;// 直接退出循环,不再执行剩余语句
if (i % 9 != 0) continue; // 跳出当前循环,执行下一次迭代
System.out.print(i + " ");
}
System.out.println();
// 3.break和continue在while中的使用
int i = 0;
while (true) {
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
*/
代码解释:在这个for循环中,i的值永远不会达到100,因为一旦i到达74,break语句就会中断循环。通常,只有在不知道中断条件何时满足时,才需要这样使用break。只要i不能被9整除,continue语句就会使执行过程返回到循环的最开头〈(这使i值递增)。如果能够整除,则将值显示出来。
无穷循环的两种形式:while(true)和for(;;)。
4.7 臭名昭著的goto
goto起源于汇编语言的程序控制:“若条件A成立,则跳到这里,否则跳到那里”。goto是源码级别的跳转,所以若一个程序总数从一个地方跳转到另一个地方,不方便识别程序的控制流程。
下面是把break和continue纳入一起讨论,因为使用了相同的机制:标签。
标签是后面跟有冒号的标识符。
label1:
在Java中,标签起作用的唯一的地方刚好是在迭代语句之前。由于break和continue关键词通常只中断当前循环,但若随同标签一起使用,它们就会中断循环,直到标签所在的地方:
label1:
outer-iteration {
inner-iteration {
//...
break; // (1)
//...
continue; // (2)
//...
continue label1; // (3)
//...
break label1; // (4)
}
}
代码解释:
-
在(1)中,break中断内部迭代,回到外部迭代。
-
在(2)中,continue使执行点移回内部迭代的起始处。
-
在(3)中,continue label1同时中断内部迭代以及外部迭代,直接转到label1处;随后,它实际上是继续迭代过程,但却从外部迭代开始。
-
在(4)中,break label1也会中断所有迭代,并回到label1处,但并不重新进入迭代。也就是说,它实际是完全中止了两个迭代。
标签在for循环中的使用:
package 第4章_控制执行流程.第7节_臭名昭著的goto;
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++;
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;
}
}
}
}
}
}
/**
* 打印结果:
* 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
*/
如果没有break outer语句,就没有办法从内部循环跳出外部循环,因为break和continue只能中断最内层的循环,所以这也是break outer的用处。
如果想在中断循环的同时退出,那么使用return即可。
标签在while循环中的使用:
package 第4章_控制执行流程.第7节_臭名昭著的goto;
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;// 当i==1时,跳出本次循环,开始下一次迭代
}
if (i == 3) {
System.out.println("continue outer");
continue outer;// 当i==3时,跳到outer外部循环处,开始下一次迭代
}
if (i == 5) {
System.out.println("break");
break;// 当i==5时,跳出当前循环,不再执行后面的语句,只是跳出了内部循环
}
if (i == 7) {
System.out.println("break outer");
break outer;// 当i==7时,跳到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会中断并跳出标签所指的循环。
注意:之所以使用标签就是因为有嵌套循环的存在,而普通的break和continue不能跳出多层嵌套。
4.8 switch
switch是根据整数表达式的值,可以从一系列代码中选择一段去执行,基本格式如下:
switch(integral-selector) {
case integral-value1:
statement;
break;
case integral-value2:
statement;
break;
case integral-value3:
statement;
break;
...
default:
statement;
}
其中integral-selector是一个能够产生整数值的表达式,switch会将这个表达式的结果与每个integral-value进行比较,如果相符则执行对应的语句,如果不相符则执行default语句。
其中break会使执行流程跳转至switch主体的末尾。但实际上break是可选的,如果省略break,那么会继续执行后面的case语句,直到遇到一个break为止。而default语句后面没有break,因为执行流程已经到了break的跳转目的地,当然也可以在default后面放置一个break,没有任何实际意义。
package 第4章_控制执行流程.第8节_switch;
public class SwitchTest {
public static void main(String[] args) {
int a = 2;
// 使用break的情况
System.out.println("使用break的情况: ");
switch (a) {
case 1:
System.out.println(1);
break;
case 2:
System.out.println(2);
break;
case 3:
System.out.println(3);
break;
default:
System.out.println("匹配失败");
}
// 不使用break的情况
System.out.println("不使用break的情况: ");
switch (a) {
case 1:
System.out.println(1);
case 2:
System.out.println(2);
case 3:
System.out.println(3);
default:
System.out.println("匹配失败");
}
}
}
/**
* 打印结果:
* 使用break的情况:
* 2
* 不使用break的情况:
* 2
* 3
* 匹配失败
*/
switch语句需要使用一个选择因子,可以是int或char那样的整数值,现在也可以是字符串值。
package 第4章_控制执行流程.第8节_switch;
import java.util.Random;
import static net.mindview.util.Print.print;
import static net.mindview.util.Print.printnb;
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';
printnb((char) c + ", " + c + ": ");
switch (c) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
print("vowel");
break;
case 'y':
case 'w':
print("Sometimes a vowel");
break;
default:
print("consonant");
}
}
}
}
/**
* 打印结果:
* y, 121: Sometimes a vowel
* n, 110: consonant
* z, 122: consonant
* b, 98: consonant
* r, 114: consonant
* n, 110: consonant
* y, 121: Sometimes a vowel
* g, 103: consonant
* c, 99: consonant
* f, 102: consonant
* o, 111: vowel
* w, 119: Sometimes a vowel
* z, 122: consonant
*/
注意:case语句进行了堆叠,即只要满足多种条件中的一个,就执行那段代码。