本章概要
- true 和 false
- if-else
- 迭代语句
- while
- do-while
- for
- 逗号操作符
- for-in 语法
程序必须在执行过程中控制它的世界并做出选择。 在 Java 中,你需要执行控制语句来做出选择。
Java 使用了 C 的所有执行控制语句,因此对于熟悉 C/C++ 编程的人来说,这部分内容轻车熟路。大多数面向过程编程语言都有共通的某种控制语句。在 Java 中,涉及的关键字包括 if-else,while,do-while,for,return,break 和选择语句 switch。 Java 并不支持备受诟病的 goto(尽管它在某些特殊场景中依然是最行之有效的方法)。 尽管如此,在 Java 中我们仍旧可以进行类似的逻辑跳转,但较之典型的 goto 用法限制更多。
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
通过上一章的学习,我们知道任何关系运算符都可以产生条件语句。 注意:在 Java 中使用数值作为布尔值是非法的,即便这种操作在 C/C++ 中是被允许的(在这些语言中,“真”为非零,而“假”是零)。如果想在布尔测试中使用一个非布尔值,那么首先需要使用条件表达式来产生 boolean 类型的结果,例如 if(a != 0)
。
if-else
if-else 语句是控制程序执行流程最基本的形式。 其中 else
是可选的,因此可以有两种形式的 if
。代码示例:
if(Boolean-expression)
“statement”
或
if(Boolean-expression)
“statement”
else
“statement”
布尔表达式(Boolean-expression)必须生成 boolean 类型的结果,执行语句 statement
既可以是以分号 ;
结尾的一条简单语句,也可以是包含在大括号 {}
内的的复合语句 —— 封闭在大括号内的一组简单语句。 凡本书中提及“statement”一词,皆表示类似的执行语句。
下面是一个有关 if-else 语句的例子。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
语句。
Java 和 C/C++ 同属“自由格式”的编程语言,但通常我们会在 Java 控制流程语句中采用首部缩进的规范,以便代码更具可读性。
迭代语句
while,do-while 和 for 用来控制循环语句(有时也称迭代语句)。只有控制循环的布尔表达式计算结果为 false
,循环语句才会停止。
while
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'");
}
}
输出结果:
condition()
方法使用到了 Math 库的静态方法 random()
。该方法的作用是产生 0 和 1 之间 (包括 0,但不包括 1) 的一个 double 值。
result 的值是通过比较运算符 <
产生的 boolean 类型的结果。当控制台输出 boolean 型值时,会自动将其转换为对应的文字形式 true
或 false
。此处 while
条件表达式代表:“仅在 condition()
返回 false
时停止循环”。
do-while
do-while 的格式如下:
do
statement
while(Boolean-expression);
while 和 do-while 之间的唯一区别是:即使条件表达式返回结果为 false
, do-while 语句也至少会执行一次。 在 while 循环体中,如布尔表达式首次返回的结果就为 false
,那么循环体内的语句不会被执行。实际应用中,while 形式比 do-while 更为常用。
for
for 循环可能是最常用的迭代形式。 该循环在第一次迭代之前执行初始化。随后,它会执行布尔表达式,并在每次迭代结束时,进行某种形式的步进。for 循环的形式是:
for(initialization; Boolean-expression; step)
statement
初始化 (initialization) 表达式、布尔表达式 (Boolean-expression) ,或者步进 (step) 运算,都可以为空。每次迭代之前都会判断布尔表达式的结果是否成立。一旦计算结果为 false
,则跳出 for 循环体并继续执行后面代码。 每次循环结束时,都会执行一次步进。
for 循环通常用于“计数”任务。代码示例:
// 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);
}
}
}
}
输出结果(前 10 行):
注意:变量 c 是在 for 循环执行时才被定义的,并不是在主方法的开头。c 的作用域范围仅在 for 循环体内。
传统的面向过程语言如 C 需要先在代码块(block)前定义好所有变量才能够使用。这样编译器才能在创建块时,为这些变量分配内存空间。在 Java 和 C++ 中,我们可以在整个块使用变量声明,并且可以在需要时才定义变量。 这种自然的编码风格使我们的代码更容易被人理解。
上例使用了 java.lang.Character 包装类,该类不仅包含了基本类型 char
的值,还封装了一些有用的方法。例如这里就用到了静态方法 isLowerCase()
来判断字符是否为小写。
逗号操作符
在 Java 中逗号运算符(这里并非指我们平常用于分隔定义和方法参数的逗号分隔符)仅有一种用法:在 for 循环的初始化和步进控制中定义多个变量。我们可以使用逗号分隔多个语句,并按顺序计算这些语句。注意:要求定义的变量类型相同。代码示例:
// 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
上例中 int 类型声明包含了 i
和 j
。实际上,在初始化部分我们可以定义任意数量的同类型变量。注意:在 Java 中,仅允许 for 循环在控制表达式中定义变量。 我们不能将此方法与其他的循环语句和选择语句中一起使用。同时,我们可以看到:无论在初始化还是在步进部分,语句都是顺序执行的。
for-in 语法
Java 5 引入了更为简洁的“增强版 for 循环”语法来操纵数组和集合。(更多细节,可参考 数组 和 集合 章节内容)。大部分文档也称其为 for-each 语法,但因为了不与 Java 8 新添的 forEach()
产生混淆,因此这里称之为 for-in 循环。 (Python 已有类似的先例,如:for x in sequence)。注意:你可能会在其他地方看到不同叫法。
for-in 无需你去创建 int 变量和步进来控制循环计数。 下面我们来遍历获取 float 数组中的元素。代码示例:
import java.util.*;
// control/ForInFloat.java
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);
}
}
}
输出结果:
上例中我们展示了传统 for 循环的用法。接下来再来看下 for-in 的用法。代码示例:
for(float x : f) {}
这条语句定义了一个 float 类型的变量 x
,继而将每一个 f
的元素赋值给它。
任何一个返回数组的方法都可以使用 for-in 循环语法来遍历元素。例如 String 类有一个方法 toCharArray()
,返回值类型为 char 数组,我们可以很容易地在 for-in 循环中遍历它。代码示例:
// 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-in 循环适用于任何可迭代(iterable)的 对象。
通常,for 循环语句都会在一个整型数值序列中步进。代码示例:
for(int i = 0; i < 100; i++){}
正因如此,除非先创建一个 int 数组,否则我们无法使用 for-in 循环来操作。为简化测试过程,已在 onjava
包中封装了 Range 类,利用其 range()
方法可自动生成恰当的数组。
Range.java
public class Range {
// Produce sequence [start..end) incrementing by step
public static int[] range(int start, int end, int step) {
if (step == 0) {
throw new IllegalArgumentException("Step cannot be zero");
}
int sz = Math.max(0, step >= 0 ? (end + step - 1 - start) / step : (end + step + 1 - start) / step);
int[] result = new int[sz];
for (int i = 0; i < sz; i++) {
result[i] = start + (i * step);
}
return result;
} // Produce a sequence [start..end)
public static int[] range(int start, int end) {
return range(start, end, 1);
}
// Produce a sequence [0..n)
public static int[] range(int n) {
return range(0, n);
}
}
在 封装(Implementation Hiding)这一章里我们介绍了静态导入(static import),无需了解细节就可以直接使用。 有关静态导入的语法,可以在 import 语句中看到:
import static BASE0002.Range.range;
// control/ForInInt.java
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()
方法有多种重载形式:第一种产生从 0 至范围上限(不包含)的值;第二种产生参数一至参数二(不包含)范围内的整数值;第三种形式有一个步进值,因此它每次的增量为该值;第四种 range()
表明还可以递减。range()
无参方法是该生成器最简单的版本。
range()
的使用提高了代码可读性,让 for-in 循环在本书中适应更多的代码示例场景。
请注意,System.out.print()
不会输出换行符,所以我们可以分段输出同一行。
for-in 语法可以节省我们编写代码的时间。 更重要的是,它提高了代码可读性以及更好地描述代码意图(获取数组的每个元素)而不是详细说明这操作细节(创建索引,并用它来选择数组元素) 本书推荐使用 for-in 语法。(注:本人曾看过一篇文章,是测试不同迭代方式的效率问题,最后结论为使用 for(int i = 0; i < 100; i++){} 方式为最优,个人推荐此方法)。